Repository: ffd8/xyscope Branch: main Commit: 74e0947c6584 Files: 57 Total size: 366.8 KB Directory structure: gitextract_zbh9fj9i/ ├── .gitignore ├── README.md ├── changelog.txt ├── examples/ │ ├── 1_getting_started/ │ │ ├── basic_drawing/ │ │ │ └── basic_drawing.pde │ │ ├── calibration/ │ │ │ └── calibration.pde │ │ ├── clock/ │ │ │ └── clock.pde │ │ ├── custom_soundcard/ │ │ │ └── custom_soundcard.pde │ │ ├── lissapong/ │ │ │ └── lissapong.pde │ │ └── template/ │ │ └── template.pde │ ├── 2_shapes/ │ │ ├── a_walkthrough_primatives/ │ │ │ └── a_walkthrough_primatives.pde │ │ ├── additive_synth_shapes/ │ │ │ └── additive_synth_shapes.pde │ │ ├── lissajous/ │ │ │ └── lissajous.pde │ │ ├── paramless_shapes/ │ │ │ └── paramless_shapes.pde │ │ ├── sphere/ │ │ │ └── sphere.pde │ │ └── torus/ │ │ └── torus.pde │ ├── 3_typography/ │ │ ├── hershey_fonts/ │ │ │ └── hershey_fonts.pde │ │ └── scopewriter/ │ │ └── scopewriter.pde │ ├── 4_inputs/ │ │ ├── fonts/ │ │ │ └── fonts.pde │ │ ├── kinect/ │ │ │ └── kinect.pde │ │ ├── obj/ │ │ │ └── obj.pde │ │ ├── svg/ │ │ │ └── svg.pde │ │ ├── syphon/ │ │ │ └── syphon.pde │ │ ├── video/ │ │ │ └── video.pde │ │ ├── webcam/ │ │ │ └── webcam.pde │ │ └── webcam_processing4/ │ │ └── webcam_processing4.pde │ ├── 5_displays/ │ │ ├── laser/ │ │ │ └── laser.pde │ │ └── vectrex/ │ │ └── vectrex.pde │ ├── 6_outputs/ │ │ ├── audio_recorder/ │ │ │ └── audio_recorder.pde │ │ └── osc_wavetables/ │ │ └── osc_wavetables.pde │ ├── 7_custom_waves/ │ │ ├── customWaves_drawingXYZ/ │ │ │ └── customWaves_drawingXYZ.pde │ │ ├── customWaves_noiseXY/ │ │ │ └── customWaves_noiseXY.pde │ │ └── setWaveforms_noise/ │ │ └── setWaveforms_noise.pde │ ├── 8_music/ │ │ ├── MidiScope/ │ │ │ └── MidiScope.pde │ │ ├── audio_filters/ │ │ │ └── audio_filters.pde │ │ └── freq_keyboard_notes/ │ │ └── freq_keyboard_notes.pde │ └── 9_misc/ │ ├── freq_amp_modulation/ │ │ └── freq_amp_modulation.pde │ └── multiscopes_class/ │ └── multiscopes_class.pde ├── license.txt ├── resources/ │ ├── README.md │ ├── build.properties │ ├── build.xml │ ├── code/ │ │ ├── ExampleTaglet.java │ │ ├── ant-contrib-1.0b3.jar │ │ └── doc.sh │ ├── library.properties │ └── stylesheet.css ├── src/ │ └── xyscope/ │ ├── XYWavetable.java │ └── XYscope.java └── web/ ├── includes/ │ ├── css/ │ │ └── styles.css │ └── js/ │ ├── highlight/ │ │ ├── a11y-dark.css │ │ ├── a11y-light.css │ │ ├── docco.css │ │ ├── github.css │ │ └── highlight.pack.js │ ├── render.js │ └── stmd.js └── index.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ distribution data lib test .DS_Store .project .classpath tmp /bin/ _sandbox examples_removed .settings ================================================ FILE: README.md ================================================ # XYscope v 3.0.0 cc [teddavis.org](https://teddavis.org) 2017 – 2023 Processing library to render vector graphics on vector displays. 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!). [teddavis.org/xyscope](http://www.teddavis.org/xyscope) [github.com/ffd8/xyscope](https://github.com/ffd8/xyscope) ## Table of Contents - [Installation](#installation) - [Audio Interfaces](#audio-interfaces) - [Vector Displays](#vector-displays) - [Getting Started](#getting-started) - [Additive Synthesis](#additive-synthesis) - [References](#references) - [Extras ](#extras) ## Installation Add via Processing's Library Manager: - Sketch menu » Import Library... » Add Library... (v4 Manage Libraries...) - Search for 'XYscope' and click `Install`. - Search for 'Minim' and click `Install`. This library relies on and is infinitely thankful for [Minim](https://github.com/ddf/Minim)! Search for and add the following libraries for various examples: [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) Add manually, [download latest release](https://github.com/ffd8/xyscope/releases/latest) and expand into `~/Processing/libraries`: ## Audio Interfaces *< $5* At 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). *< $20* For 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. *> $150+* Many 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). ### List Check which interface is plugged in and how to reference it. ``` xy.getMixerInfo(); // lists available audio devices ``` ### Select By default XYscope uses the system settings audio-output, however we can specify a custom sound card (Digital Analog Converter – DAC) and sample rate. ``` xy = new XYscope(this); // system settings soundcard xy = new XYscope(this, "MOTU 1_2"); // specify a soundcard/channel to use xy = new XYscope(this, 96000); // custom sample rate ( > finer detail if card supports) xy = new XYscope(this, "MOTU 1_2", 96000); // custom soundcard, custom sample rate ``` Or select a previous XYscope instance as the output for additive-synthesis: ``` xy2 = new XYscope(this, xy.outXY); // send 2nd instance to 1st ``` ### Aggregate Device 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: - Press `CMD + Spacebar` (opens Spotlight Search) - Type `Audio Midi Setup`, press `ENTER` - Click `+` in lower left, select `Create Aggregate Device` - Tick checkbox of your multi-channel audio device - Click `Configure Speakers...` in lower right - Set configuration to `Stereo`, select channels desired - Rename to something like: `MOTU 1_2` - Repeat above for each stereo pair, ie, `MOTU 3_4`, `MOTU 5_6`... - They can now be selected by name when initializing XYscope! ### Multi-Channel Output Device Within `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. - Press `CMD + Spacebar` (opens Spotlight Search) - Type `Audio Midi Setup`, press `ENTER` - Click `+` in lower left, select `Create Multi-Channel Device` - Tick checkbox of best device first, then select additional - Set sample rate to highest available, 48000 or 96000+ - Rename for clarity, ie `DAC + SPEAKERS` or `BLACKHOLE + SPEAKERS`. ## Vector Displays Now we need a vector display to see our glowing output! ### Virtual [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. ### Analog Oscilloscope 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 [`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). ### X-Y Monitor 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. ### Vectrex A 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. XYscope has a special mode when using a Vectrex for the aspect ratio. See example `vextrex` within `5_displays`, but the main notes are: ``` /* within setup() */ xy.vectrex(90); // -90/90 for landscape, 0 for portrait // optionally z-axis for blanking xy.z("MOTU 3-4"); // use custom 3rd channel for z-axis //xy.zRange(.5, 0); // set min/max values for beam on/off ``` ### Laser 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 [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: ``` /* within setup() */ // only stereo pairs in processing, so it's broken to R, GB // incase 2nd channel of R pair is useful for blanking/etc. xy.laser("MOTU 3-4", "MOTU 5-6"); //xy.laser(mixerR, mixerGB); /* within setup() or draw() */ // RGB waveforms have own freq, so we can them out of sync xy.strokeFreq(50.05, 50.1, 50.2); xy.strokeDash(8); // optional dashes in the RGB stroke xy.stroke(0, 255, 255); // set RGB stroke before shapes (0-255) xy.limitPath(0); // avoid drawing any forms beyond view ``` ## Getting Started Our basic template for an XYscope sketch involves: ``` import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance void setup(){ size(512, 512); // window size, optionally add P3D xy = new XYscope(this, ""); // define XYscope instance, "custom_dac" optional xy.getMixerInfo(); // lists all audio devices } void draw(){ background(0); xy.clearWaves(); // clear waves from previous drawing // draw shapes here xy.circle(width/2, height/2, width); // it all began with a circle.... xy.buildWaves(); // build shapes to audio waves xy.drawAll(); } ``` ## Additive Synthesis 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. ``` import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy, xy2; // create 2x XYscope instances void setup() { size(512, 512); // declares size of output window xy = new XYscope(this); xy2 = new XYscope(this, xy.outXY); // patch 2nd instance to 1st output } void draw() { background(0); // set main shapes xy.clearWaves(); xy.circle(width/2, height/2, width/2); xy.buildWaves(); xy.drawPath(0, 255, 255); // custom stroke color xy.drawXY(); // displays combined output // additive synth on 2nd instance of XYscope xy2.clearWaves(); xy2.freq(mouseX); // without speakers, test really high freqs! //xy2.amp(.2); // experiment with high freq, low amp modulation xy2.circle(width/2, height/2, mouseY); xy2.buildWaves(); xy2.drawPath(255, 255, 0); // custom stroke color } ``` Additional tips: - No need to stop at just 2, add as many as needed! - Ratio between freqs is crucial, diff of +/- .1 animates things. - Really low frequencies animate shapes over that path. - Really high frequencies display shape made of 2nd shape. - Play with position of 2nd shape, from center to corners. ## References 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: `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. - [Initialize XYscope](#initialize-xyscope) - [Z-axis](#z-axis) * [z()](#z()) * [zAuto()](#zauto()) - [Audio Interface Settings](#audio-interface-settings) * [getMixerInfo()](#getmixerinfo()) * [sampleRate()](#samplerate()) * [bufferSize()](#buffersize()) - [Waves](#waves) * [resetWaves()](#resetwaves()) * [clearWaves()](#clearwaves()) * [buildWaves()](#buildwaves()) * [buildX()](#buildx()) * [buildY()](#buildy()) * [buildZ()](#buildz()) * [waveSize()](#wavesize()) * [setWaveforms()](#setwaveforms()) - [Points](#points) * [wavePoints()](#wavepoints()) * [steps()](#steps()) * [limitPoints()](#limitpoints()) * [limitPath()](#limitpath()) - [Record Audio](#record-audio) * [recorderBegin()](#recorderbegin()) * [recorderEnd()](#recorderend()) - [Primitive Shapes](#primitive-shapes) * [point()](#point()) * [line()](#line()) * [square()](#square()) * [rect()](#rect()) * [circle()](#circle()) * [ellipse()](#ellipse()) * [complex shape](#complex-shape) * [lissajous()](#lissajous()) * [box()](#box()) * [sphere()](#sphere()) * [ellipsoid()](#ellipsoid()) * [torus()](#torus()) - [Text](#text) * [text()](#text()) * [textPaths()](#textpaths()) * [textFont()](#textfont()) * [textSize()](#textsize()) * [textLeading()](#textleading()) * [textAlign()](#textalign()) * [textWidth()](#textwidth()) - [Modulation](#modulation) * [freq()](#freq()) * [amp()](#amp()) * [pan()](#pan()) - [Vectrex](#vectrex) - [Laser](#laser) - [Rendering](#rendering) - [Vars](#vars) --- ### Initialize XYscope ``` xy = new XYscope(this); // system audio settings xy = new XYscope(this, "custom_dac"); // custom audio card xy = new XYscope(this, xy_instance.outXY); // another XYscope out xy = new XYscope(this, sampleRate); // default sampleRate is 41000 xy = new XYscope(this, "custom_dac", sampleRate); // default sampleRate is 41000 xy = new XYscope(this, "custom_dac", sampleRate, bufferSize); // default bufferSize is 512 ``` --- ### Z-axis #### z() ``` xy.z("custom_dac"); // set audio out for z-axis (blanking) xy.z("custom_dac", sampleRate); // add custom sampleRate ``` #### zAuto() ``` // somewhat deprecated.. may return xy.zAuto(); // get setting for using z-axis xy.zAuto(boolean); // set auto use of z-axis ``` --- ### Audio Interface Settings #### getMixerInfo() ``` xy.getMixerInfo(); // list connected audio interfaces ``` #### sampleRate() ``` xy.sampleRate(); // get current sampleRate, default 41000 xy.sampleRate(newRate); // set new sampleRate (int) ``` #### bufferSize() ``` xy.bufferSize(); // get current bufferSize, default 512 xy.bufferSize(newSize); // set new bufferSize (int) ``` --- ### Waves #### resetWaves() ``` xy.resetWaves(); // fix any sync issues with phase of waves ``` #### clearWaves() ``` xy.clearWaves(); // clear wavetables from any previous drawing ``` #### buildWaves() ``` xy.buildWaves(); // compile drawn shapes into new wavetables/audio xy.buildWaves(-1); // draw in v1 style xy.buildWaves(-2); // draw in v2 style xy.buildWaves(-3); // draw in v3 style ``` #### buildX() ``` xy.buildX(newWave); // build wavetableX with float[] array ``` #### buildY() ``` xy.buildY(newWave); // build wavetableY with float[] array ``` #### buildZ() ``` xy.buildZ(newWave); // build wavetableZ with float[] array ``` #### waveSize() ``` xy.waveSize(); // get current size of wavetables xy.waveSize(newSize); // set new wavetable size, default is 512 ``` #### setWaveforms() ``` xy.setWaveforms(wfX, wfY, wfZ); // set wavetables with float[] arrays ``` --- ### Points #### wavePoints() ``` xy.wavePoints(); // get PVector ArrayList of all drawn coordinates (0.0 – 1.0) ``` #### steps() ``` xy.steps(); // get current steps between points, default 24 xy.steps(newVal); // set step multiplier, segments, between points ``` #### limitPoints() ``` xy.limitPoints(); // get number of limited drawing points xy.limitPoints(newLimit); // set new limit of points for drawing ``` #### limitPath() ``` xy.limitPath(); // get current limit of drawn path xy.limitPath(newLimit); // avoid drawn vectors beyond border edges (px) ``` --- ### Record Audio 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. #### recorderBegin() ``` xy.recorderBegin(); // will name file "XYscope_timestamp.wav" xy.recorderBegin("customName"); // set name, timestamp added ``` #### recorderEnd() ``` xy.recorderEnd(); // complete recording ``` --- ### Primitive Shapes Most 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. #### point() ``` xy.point(); xy.point(x, y); xy.point(x, y, z); ``` #### line() ``` xy.line(); xy.line(x1, y1, x2, y2); xy.line(x1, y1, z1, x2, y2, z2); ``` #### square() ``` xy.rectMode(); // default CORNER, CENTER to draw center out xy.square(); xy.square(x, y, w); ``` #### rect() ``` xy.rectMode(); // default CORNER, CENTER to draw center out xy.rect(); xy.rect(x, y, w); xy.rect(x, y, w, h); ``` #### circle() ``` xy.ellipseDetail(); // get current facets of ellipse xy.ellipseDetail(newVal); // set new count of facets, default 30 xy.circle(); xy.circle(x, y, w); ``` #### ellipse() ``` xy.ellipseDetail(); // get current facets of ellipse xy.ellipseDetail(newVal); // set new count of facets, default 30 xy.ellipse(); xy.ellipse(x, y, w); xy.ellipse(x, y, w, h); ``` #### complex shape ``` xy.beginShape(); xy.vertex(); xy.vertex(x, y); xy.vertex(x, y, z); // if in P3D mode // ... xy.endShape(); xy.endShape(CLOSE); // closes form ``` #### lissajous() ``` xy.lissajous(); xy.lissajous(x, y, radius, ratioA, ratioB, phase, resolution); ``` #### box() ``` xy.box(); xy.box(size); xy.box(rx, ryz); xy.box(rx, ry, rz); ``` #### sphere() ``` xy.sphere(); xy.sphere(size); xy.sphere(size, detailXY); // default 24 xy.sphere(size, detailX, detailY); // default 24, 24 ``` #### ellipsoid() ``` xy.ellipsoid(); xy.ellipsoid(rx, ry, rz); xy.ellipsoid(rx, ry, rz, detailXY); // default 24 xy.ellipsoid(rx, ry, rz, detailX, detailY); // default 24, 24 ``` #### torus() ``` xy.torus(); xy.torus(radius, tubeRadius); xy.torus(radius, tubeRadius, detailXY); // default 24 xy.torus(radius, tubeRadius, detailX, detailY); // default 24, 24 ``` --- ### Text #### text() ``` xy.text(); // paramless text drawing xy.text("string", x, y); // use '\n' for multi-line text ``` #### textPaths() Use coordinates of Hershey text for manipulating type! ``` xy.textPaths("string", x, y); // return 2D Array of PVector coords ``` #### textFont() ``` println(xy.fonts); // print list of avilable Hershey fonts xy.textFont("fontname"); // set active Hershey font ``` #### textSize() ``` xy.textSize(); // get current textSize xy.textSize(newSize); // set new textSize ``` #### textLeading() ``` xy.textLeading(); // get current textLeading xy.textLeading(newSize); // set new textLeading ``` #### textAlign() ``` xy.textAlign(hAlign); // Horz: LEFT (default) / CENTER / RIGHT xy.textAlign(hAlign, vAlign); // Vert options: TOP / CENTER / BOTTOM ``` #### textWidth() ``` xy.textWidth(int); // from 32 characters, get width of char xy.textWidth("string"); // get width of text for positioning ``` --- ### Modulation #### freq() Frequency of oscillators ``` // get xy.freq(); // get PVector (.x, .y, .z) of freqs xy.freq().x; // get frequency of x oscillator // set xy.freq(freqXY); // default is 50.0 xy.freq(freqX, freqY); // set x, y frequencies xy.freq(freqX, freqY, freqZ); // set x, y, z frequencies xy.freq(PVector freqXYZ); // set as PVector xy.resetWaves(); // occasionally needed if they slip out of phase ``` #### amp() Amplitude of oscillators ``` // get xy.amp(); // get PVector (.x, .y, .z) of amps xy.amp().x; // get amplitude of x oscillator // set xy.amp(ampXY); // default is 1.0 xy.amp(ampX, ampY); // set x, y to specific amplitudes xy.amp(ampX, ampY, ampZ); // set x, y, z to specific amplitudes xy.amp(PVector ampXYZ); // set as PVector ``` #### pan() Optionally swap the hard pan of XY rather than physical cables ``` xy.pan(leftPan, rightPan); // default is -1.0, 1.0 ``` --- ### Vectrex ``` xy.vectrex(0); // 0 for portrait, -90/90 for landscape orientation xy.vectrex(vw, vh, vamp, vrot); // custom width, height, amp, rotation xy.vectrexRatio(); // get current ratio xy.vectrexRatio(newRatio); // set new ratio, default is .82 ``` --- ### Laser ``` xy.laser("dac_Red", "dac_GreenBlue"); // custom 2ch pairs for R, GB // be cautious of laser galvos, can help smooth graphics xy.laserLPF(); // get value of laser's LowPassFilter xy.laserLPF(newLPF); // set mew value of LowPassFilter (0.1 – 20000.0) // avoid dangerous hotspot, only draw laser if shape is big enough xy.spotKiller(); // get current min size of drawing for laser xy.spotKiller(newVal); // set min drawing size to prevent laser hotspot xy.stroke(r, g, b); // set RGB laser stroke color xy.stroke(PVector rgb); // set as PVector xy.strokeFreq(); // get laser RGB channel frequencies xy.strokeFreq(freqRGB); // set laser RGB freq as group xy.strokeFreq(freqR, freqG, freqB); // set laser R, G, B separately xy.strokeFreq(PVector freqRGB); // set as PVector // some lasers ignore values below __ when setting colors, // use this to set lowest point when color mixing xy.strokeMin(); // get curret min RGB values for laser stroke xy.strokeMin(minR, minG, minB); // set new min values (0.0 - 255.0) xy.strokeMin(PVector minPV); // set new min as PVector xy.strokeWB(wbR, wbG, wbB); // set values needed for white light xy.strokeWB(PVector wbRGB); // set values as PVector xy.strokeDash(); // get current dash setting xy.strokeDash(sdRGB); // set laser dash count for RGB strokes xy.strokeDash(sdR, sdG, sdB); // set dashes per R, G, B xy.strokeDash(PVector sdRGB); // set as PVector ``` --- ### Rendering ``` xy.drawAll(); // all views below xy.drawPath(); // shapes being drawn xy.drawPoints(); // points along paths of drawing xy.drawWaveform(); // drawing as oscillator's waveform xy.drawWave(); // waveform over time at frequency xy.drawXY(); // simulated xy-mode oscilloscope xy.debugView(); // check if debugView active xy.debugView(boolean); // compare drawXY() to drawWaveform() ``` ### Vars Lastly, many variables within XYscope are public for your vector hacking needs. ``` shapes; // XYShapeList, processed by buildWaves() minim, minimZ; // instances of Minim recorder; // instance for audio recording outXY, outZ; // AudioOutput (used to patch for additive-synth) sumXY, sumZ; // Summer for patching filters waveX, waveY, waveZ; // Oscil for each XYZ oscillators tableX, tableY, tableZ; // XYWavetable, applied to oscillator shapeX, shapeY, shapeZ; // float[] used for wavetables fonts; // list of Hershey fonts minimR, minimBG; // instances of Minim waveR, waveG, waveB; // Oscil for each RGB oscillators tableR, tableG, tableB; // XYWavetable, applied to oscillator shapeR, shapeG, shapeB; // float[] used for wavetables ``` ## Extras ### Contributing Found a bug, missing feature, and/or created a project with XYscope? Let me know! Create an [issue on GitHub](https://github.com/ffd8/xyscope/issues). ### License This project is licensed under the LGPL License - see [LICENSE.md](https://github.com/ffd8/xyscope/blob/master/license.txt) for details. ### Shoutouts * [Just Van Rossum](http://dailydrawbot.tumblr.com), the enlightening conversation on my X-Y attempts. * [Stefanie Bräuer](https://stefaniebraeuer.ch/), feeding the obsession with crucial theory + context. * [Hansi Raber](https://asdfg.me), java meta insights + finding external WaveTable bug! * [Processing Library Template](https://github.com/processing/processing-library-template) ================================================ FILE: changelog.txt ================================================ + new * changed - removed //////////////////////////////////////////////////////////// XYScope 3.0.0 (REV 5) - 21.02.2023 + NEWER & IMPROVEDER buildWaves() technique (added -3, to those who like previous) + Added Hershey Fonts (single line) w/ many functions (textFont, textSize, textAlign, textPaths, textWidth, textLeading, multi-line text, ...) + Added circle() + square() to match p5/Processing + Added steps() for multiplier of segments between points + resetWaves() alias of waveReset(), to keep style of clearWaves() and buildWaves() + XYZ WaveTables are public to grab values for other usage + pan(left, right) to swap pans of channels incase cables physically swapped + box(), sphere(), ellipsoid(), torus() 3D primitives added + lissajous(), to draw such forms + primitives can be called without params for quick test, ie xy.circle() + audio recorder, to save visuals as .wav! + lots of new demos, sharing tricks/tips from past years playing with it + newly designed references as tutorial website * ellipse() + rect() now work with 3 params like Processing * ellipse()/circle() fixed connecting point * point() fixed * more functions accept float for random/sin play * limitPath() improved to only draw necessary vector points * endShape() now has CLOSE * drawXY(), drawPath(), drawAudio(), accept r,g,b values for custom coloring * ellipseDetail() abs() for negative numbers //////////////////////////////////////////////////////////// XYScope 2.2.0 (REV 4) - 20.12.2018 + NEW & IMPROVED buildWaves() technique (added -2, to those who like previous) + Fixed Minim WaveTable bug that caused crash since REV 2 (thanks Hansi Raber!) + limitPath() only draw if shape within border from edges + Laser: RGB Laser compatibility (using 2x stereo audio pairs for RGB control) + Laser: stroke() to control RGB color + Laser: strokeFreq() to control RGB frequencies + Laser: strokeDash() to add dashes to RGB waves + Laser: spotKiller() to only draw if image bigger than spotKiller() size (default 20) + Laser: laserLPF() default low-pass-filter is 10k hz, use laserLPF() to customize + Examples: added xtra_obj + xtra_video + xtra_laser * minor vertex() code changes * minor changes to examples //////////////////////////////////////////////////////////// XYScope 2.1.0 (REV 3) - 25.07.2018 + Vectrex compatibility (custom aspect ratio and +/- 90° rotation) + Vectrex example * BuildWaves + drawXY to work with Vectrex mode //////////////////////////////////////////////////////////// XYScope 2.0.0 (REV 2) - 08.05.2018 Added/dropped/refactored enough features to need 2.0 + Brand new drawing to waveform technique (smoother)! Prefer old way? use buildWaves(-1) If points matches buffer (1024), previously technique is used to maintain speed. + debugView(), for seeing drawWaveform() to drawXY() relationship (thanks Hansi3D!) + waveSize() function for dynamically changing wavetable size + XYscope instance for custom sample rates (41000, 48000, 96000, 192000, etc) + waveReset() now public incase time-step of oscillators slip when changing freq() * Technique for adding z-axis, * Refactored audio out, so minim filters (lowpass) can be used, see xtra_filters example * Minor bugs in drawWaveform() rendering * Drawings collect as a shapesList vs arrayList, clever suggestion by Hansi3D * Stopped performing waveReset() on freq change, which preventing nice beam walk-around - XYscope instance with z-axis, now has dedicated function - XYscope instance with int for mixerID, can change too often, select mixer by name - addPoint(), just use point() - wavePoints(set), new structure doesn't allow, use custom buildWaves() - sortPoints(), was a rough test, do within sketch instead - freqX(), freqY(), freqZ(), ampX(), ampY(), ampZ() now combined into freq(), amp() for 1 + optional 2, 3 values //////////////////////////////////////////////////////////// XYScope 1.0.2 (REV 1) - 11.09.2017 + audioMix as instance output, enabling additive-synthesis //////////////////////////////////////////////////////////// XYScope 1.0.1 (REV 1) - 91.08.2017 + xtra_syphon example, similar to webcam, load syphon textures //////////////////////////////////////////////////////////// XYScope 1.0.0 (REV 1) - 21.06.2017 XYscope enters the world! + Everything ================================================ FILE: examples/1_getting_started/basic_drawing/basic_drawing.pde ================================================ /* basic_drawing Draw to the scope by throwing more and more lines, clearing on demand cc teddavis.org 2017-23 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance void setup() { size(512, 512); xy = new XYscope(this); // define XYscope instance //xy.getMixerInfo(); // lists all audio devices } void draw() { background(0); xy.buildWaves(); // build audio from shapes xy.drawAll(); // draw all analytics } void mouseDragged() { // add point based on width/height xy.line(mouseX, mouseY, pmouseX, pmouseY); } void keyPressed() { if (keyCode == 8) { // DELETE xy.clearWaves(); // clear waves similar to background } } ================================================ FILE: examples/1_getting_started/calibration/calibration.pde ================================================ /* calibration circle + square + lines to help center/adjust oscilloscpe display cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance void setup() { size(512, 512); xy = new XYscope(this); // define XYscope instance //xy.getMixerInfo(); // lists all audio devices } void draw() { background(0); xy.clearWaves(); // clear waves similar to background pushMatrix(); translate(width/2, height/2); pushMatrix(); rotate(radians(180)); xy.circle(0, 0, width); popMatrix(); xy.rectMode(CENTER); xy.rect(0, 0, width); xy.line(-width/2, 0, width/2, 0); xy.line(0, height/2, 0, -height/2); popMatrix(); xy.buildWaves(); // build audio from shapes xy.drawAll(); // draw all analytics } ================================================ FILE: examples/1_getting_started/clock/clock.pde ================================================ /* clock cc teddavis.org 2018-23 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance float sec = 0; void setup() { size(512, 512); xy = new XYscope(this); // define XYscope instance //xy.getMixerInfo(); // lists all audio devices sec = map(second(), 0, 60, 0, 360); } void draw() { background(0); xy.freq(second()); //xy.freq(sec%360/6); // smooth ramp xy.ellipseDetail(24); // clear waves like refreshing background xy.clearWaves(); // draw clock face xy.ellipse(width/2, height/2, width, width); pushMatrix(); translate(width/2, height/2); for (int i=0; i < 12; i++) { rotate(radians(360/12)); xy.line(0, height*.49, 0, height*.4); } popMatrix(); //second float s = map(second(), 0, 60, 0, 360); sec += .1;//24/millis()/1000; pushMatrix(); translate(width/2, height/2); rotate(radians(s)); // sec for smooth xy.line(0, -height*.35, 0, 0); popMatrix(); //minute float m = map(minute(), 0, 60, 0, 360); pushMatrix(); translate(width/2, height/2); rotate(radians(m)); xy.line(0, -height*.25, 0, 0); popMatrix(); //hour float h = map(hour(), 0, 24, 0, 360); pushMatrix(); translate(width/2, height/2); rotate(radians(h)); xy.line(0, -height*.15, 0, 0); popMatrix(); // build audio from shapes xy.buildWaves(); // draw all analytics xy.drawAll(); } ================================================ FILE: examples/1_getting_started/custom_soundcard/custom_soundcard.pde ================================================ /* custom_soundcard Most AC-Coupled DACs will wiggle towards the center. For 'better' results, find a DC-coupled DAC. http://www.expert-sleepers.co.uk/siwacompatibility.html + cheapo $20 solution: https://www.delock.com/produkt/61645/merkmale.html https://www.youtube.com/live/VjRTLviVBxo?feature=share&t=2491 cc teddavis.org 2017-23 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance void setup() { size(512, 512); xy = new XYscope(this); // init XYscope with default sound out xy.getMixerInfo(); // list available sound cards //xy = new XYscope(this, 0); // select by port //xy = new XYscope(this, "BlackHole 2ch"); // select by name //xy = new XYscope(this, "BlackHole 2ch", 48000); // custom card + sampeRate } void draw() { background(0); xy.clearWaves(); // clear waves like refreshing background xy.rectMode(CENTER); xy.rect(mouseX, mouseY, width/4, width/4); xy.buildWaves(); // build audio from shapes xy.drawAll(); // draw all analytics } ================================================ FILE: examples/1_getting_started/lissapong/lissapong.pde ================================================ /* lissapong Let the epic crt battle over lissajous begin! mouseY, move user paddle cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance boolean autoPlay = true, gameover = true, initScore = false, demoMode = true; PVector c, p; float cx = 1, cy = 2, s = 30, f = 10.5, paddleW = 10, paddleH = 150; int gameTimer = 0, score1 = 0, score2 = 0, winner = 0; void setup() { size(512, 512); xy = new XYscope(this); xy.rectMode(CENTER); pongReset(); } void draw() { background(0); xy.clearWaves(); // render scores float s1 = 25; float s2 = 25; if (winner == -1) { s1 += sin(frameCount*.5)*20; } else if (winner == 1) { s2 += sin(frameCount*.5)*20; } xy.textSize(s1); xy.text(score1, 50, 50); xy.textSize(s2); xy.text(score2, width-50, 50); // gameplay vs gameover if (!gameover) { pongUpdate(); xy.lissajous(c.x, c.y, s, cx, cy, frameCount, 60); float ly = c.y + (noise(frameCount*.023)*height-c.y)*c.x/width; xy.rect(paddleW/2, ly, paddleW, paddleH); float ry = c.y + (noise(frameCount*.02)*height-c.y)*(1 - c.x/width); if (!autoPlay) { ry = p.y; } xy.rect(width-paddleW/2, ry, paddleW, paddleH); if (frameCount%30==0 && autoPlay) { demoMode = !demoMode; } if (demoMode && autoPlay) { xy.textSize(15); xy.textAlign(CENTER, CENTER); xy.text("CLICK TO PLAY", width/2, 50); } } else { pushMatrix(); xy.textSize(40 + sin(frameCount*.05)*20); xy.textAlign(CENTER, CENTER); translate(width/2, height/2); rotate(radians(cos(frameCount*.01)*15)); xy.text("GAME OVER", 0, 0); popMatrix(); } xy.buildWaves(); xy.drawXY(); xy.drawWaveform(); } void mousePressed() { if (gameover) { pongReset(); } if (autoPlay) { autoPlay = false; pongReset(); } } void pongReset() { gameover = false; c = new PVector(width/2, height/2); p = new PVector(0, c.y); cx = 1; cy = 2; xy.freq(cx * cy * 10); f = 10.5; gameTimer = 0; score1 = 0; score2 = 0; winner = 0; paddleH = 150; demoMode = true; } void pongUpdate() { p.y = mouseY; c.x += cx; c.y += cy; if (!autoPlay && (abs(c.y-p.y) > (paddleH/2 + s) && c.x >= (width-paddleW/2-s)) ) { println(abs(c.y - p.y) + " / "+ (paddleH/2 + s)); gameover = true; if (score1 >= score2) { winner = -1; } else { winner = 1; } xy.freq(random(4, 20)); xy.resetWaves(); } if ((c.x > (width-paddleW/2-s) || c.x < (s-paddleW))) { changeX(); changeY(); xy.freq(cx*cy); xy.resetWaves(); cx *= -1; initScore = true; if (paddleH > paddleW) { paddleH--; } } if (c.y > height-s/2 || c.y < s/2) { cy *= -1; } if (cy == height-s/2) { cy -= s/2; } else if (cy == s/2) { cy += s/2; } gameTimer++; } void changeX() { float fx = ceil(random(1, 10)); if (c.x < s) fx *=-1; if (fx > 0) { score2++; } else { score1++; } cx = fx; } void changeY() { float fy = ceil(random(1, 10)); if (!autoPlay && c.x > width/2) { fy = abs(c.y - p.y)/10; } if (c.y > height-s/2) { fy *=-1; } cy = fy; } ================================================ FILE: examples/1_getting_started/template/template.pde ================================================ /* template Recommended template for Processing when working with XYscope often. Save into ~/Documents/Processing/templates/Java/ (as `sketch.pde`) cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance void setup(){ size(512, 512); // window size, optionally add P3D xy = new XYscope(this, ""); // define XYscope instance, "custom_dac" optional xy.getMixerInfo(); // lists all audio devices } void draw(){ background(0); xy.clearWaves(); // clear waves from previous drawing // draw shapes here xy.circle(width/2, height/2, width); // it all began with a circle.... xy.buildWaves(); // build shapes to audio waves xy.drawWaveform(); xy.drawXY(); } ================================================ FILE: examples/2_shapes/a_walkthrough_primatives/a_walkthrough_primatives.pde ================================================ /* a_walkthrough_primatives demo-run of available shapes cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance int shapeSel = 0; void setup() { size(512, 512, P3D); // initialize XYscope with default sound out xy = new XYscope(this); } void draw() { background(0); xy.limitPath(0); // limit drawing any points outside of view xy.steps(100); // interpolated points between points // clear waves like refreshing background xy.clearWaves(); if (frameCount%240==0) { shapeSel++; } pushMatrix(); translate(width/2, height/2); // use most primative shapes with class instance infront switch(shapeSel%10) { case 0: xy.freq(1); xy.point(random(-width/2, width/2), random(-height/2, height/2)); break; case 1: xy.freq(12); for (int i=0; i<4; i++) { pushMatrix(); rotate(radians(frameCount*i*.2)); float lineLen = sin(frameCount*.01 + i*1)*width/2; xy.line(0, -lineLen, 0, lineLen); popMatrix(); } break; case 2: float efreq = frameCount*.1%30;//sin(frameCount*.005)*30; xy.freq(25); for (int i=0; i < 3; i++) { pushMatrix(); rotate(radians(i*120+frameCount*.1)); translate(width/4, 0); xy.ellipseDetail(noise(i*2+frameCount*.01)*12); xy.ellipse(0, 0, width/4); popMatrix(); } break; case 3: xy.freq(50); xy.rectMode(CENTER); pushMatrix(); rotateY(radians(frameCount)); xy.rect(0, 0, width/2, height/2); rotateX(radians(frameCount)); xy.rect(0, 0, width/3, height/3); popMatrix(); break; case 4: xy.freq(75); //randomSeed(1); pushMatrix(); rotateY(radians(frameCount/3)); xy.beginShape(); for (int i=0; i < 10; i++) { float d = width/2; 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); } xy.endShape(CLOSE); popMatrix(); break; case 5: xy.freq(50); xy.lissajous(0, 0, height/2, 1, 2, frameCount, 180); break; case 6: xy.freq(50); pushMatrix(); rotateY(radians(frameCount)); rotateX(radians(frameCount/2)); float bsize = width/2; xy.box(sin(frameCount*.01)*bsize, bsize, cos(frameCount*.01)*bsize); popMatrix(); break; case 7: pushMatrix(); rotateY(radians(frameCount)); xy.sphere(width/2.5, floor(noise(frameCount*.01)*24), floor(noise(10+frameCount*.011)*24)); popMatrix(); break; case 8: pushMatrix(); rotateY(radians(frameCount)); xy.torus(width/4, width/6, floor(noise(frameCount*.01)*12), floor(noise(10+frameCount*.011)*24)); popMatrix(); break; case 9: xy.freq(8); xy.textAlign(CENTER, CENTER); xy.textSize(50 + sin(frameCount*.025)*25); pushMatrix(); xy.text("XYscope", 0, sin(frameCount*.05)*height/3); popMatrix(); break; } popMatrix(); // build audio from shapes xy.buildWaves(); // draw analytics xy.drawWaveform(); xy.drawXY(); } void keyPressed() { if (key == ' ') { shapeSel++; } } ================================================ FILE: examples/2_shapes/additive_synth_shapes/additive_synth_shapes.pde ================================================ /* additive-synth shapes an amazing aspect of vector graphics, is sending multiple shapes on the same audio = additive synth! create wild forms, animation, effects, through various freq/amp play. cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy, xy2; // create 2x XYscope instances void setup() { size(512, 512); // declares size of output window xy = new XYscope(this); xy2 = new XYscope(this, xy.outXY); // patch 2nd instance to 1st output //xy.getMixerInfo(); // lists all audio devices } void draw() { background(0); xy.clearWaves(); xy.circle(width/2, height/2, width/2); xy.buildWaves(); xy.drawPath(0, 255, 255); // custom stroke color xy.drawXY(); // additive synth on 2nd instance of XYscope xy2.clearWaves(); xy2.freq(mouseX); // without speakers, test really high freqs! //xy2.amp(.2); // experiment with high freq, low amp modulation //xy2.circle(width/2, height/2, mouseY); xy2.circle(mouseX, mouseY, 50); //xy2.line(width/2, height/2, mouseX, mouseY); //xy2.lissajous(width/2, height/2, mouseY/2, 1, 2, frameCount, 180); xy2.buildWaves(); xy2.drawPath(255, 255, 0); // custom stroke color } ================================================ FILE: examples/2_shapes/lissajous/lissajous.pde ================================================ /* shapes_lissajous Sometimes you just want some lissajous curves! cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance void setup() { size(512, 512); background(0); // initialize custom local minim xy = new XYscope(this); } void draw() { background(0); xy.clearWaves(); // clear waves int res = 180; if(mousePressed){ res = mouseX/2; println("resolution: " + res); } // xy.lissajous(xPos, yPos, radius, ratioA, ratioB, phase, resolution); xy.lissajous(width*.25, height*.25, height/4.5, 1, 1, frameCount, res); xy.lissajous(width*.75, height*.25, height/4.5, 1, 2, frameCount, res); xy.lissajous(width*.25, height*.75, height/4.5, 1, 3, frameCount, res); xy.lissajous(width*.75, height*.75, height/4.5, 1, 4, frameCount, res); xy.buildWaves(); // build waves xy.drawAll(); // draw analytics } ================================================ FILE: examples/2_shapes/paramless_shapes/paramless_shapes.pde ================================================ /* paramless shapes incase you're quickly testing/sketching, you can create primatives without values cc teddavis.org 2023 */ import xyscope.*; XYscope xy; import ddf.minim.*; void setup() { size(512, 512, P3D); // declares size of output window xy = new XYscope(this); //xy.getMixerInfo(); // lists all audio devices } void draw() { background(0); if (frameCount%60==0) { xy.freq(random(-100, 100)); xy.resetWaves(); // helps if slipping out of phase from freq changes } xy.clearWaves(); switch(floor(frameCount*.1)%10) { case 0: xy.line(); break; case 1: xy.rectMode(CENTER); xy.rect(); break; case 2: xy.square(); break; case 3: xy.circle(); break; case 4: xy.ellipse(); break; case 5: xy.lissajous(); break; case 6: xy.textAlign(CENTER, CENTER); xy.textSize(80); xy.text(); break; case 7: pushMatrix(); translate(width/2, height/2); xy.box(); popMatrix(); break; case 8: pushMatrix(); translate(width/2, height/2); xy.sphere(); popMatrix(); break; case 9: pushMatrix(); translate(width/2, height/2); xy.torus(); popMatrix(); break; } xy.buildWaves(); xy.drawXY(); } ================================================ FILE: examples/2_shapes/sphere/sphere.pde ================================================ /* sphere mouseX/Y, adjust detailX, detailY SHIFT + mouseDragged, freq based on distance from initial click */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance int pmx = 0, pmy = 0; void setup() { size(512, 512, P3D); // declares size of output window xy = new XYscope(this); //xy.getMixerInfo(); // lists all audio devices } void draw() { background(0); xy.clearWaves(); //xy.resetWaves(); if (keyPressed && keyCode == 16) { // SHIFT float freq = dist(mouseX, mouseY, pmx, pmy); xy.freq(freq); } // draw shapes here pushMatrix(); translate(width/2, height/2); rotateY(radians(frameCount/3)); rotateX(radians(frameCount/2)); xy.sphere(width*.4, mouseX/5, mouseY/5); // xy.sphere(size, xVert, yVert) popMatrix(); xy.buildWaves(); xy.drawXY(); } void mousePressed() { pmx = mouseX; pmy = mouseY; } ================================================ FILE: examples/2_shapes/torus/torus.pde ================================================ /* shape_torus Based upon Ira Greenbergs Toroid example https://processing.org/examples/toroid.html - mouseX » adjust dx - mouseY » adjust dy cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // initial detailX, detailY values int dx = 12, dy = 12; void setup() { size(512, 512, P3D); // initialize XYscope with default sound out xy = new XYscope(this); } void draw() { background(0); xy.clearWaves(); // clear waves //center and spin toroid pushMatrix(); translate(width/2, height/2, -100); rotateX(radians(frameCount/2)); rotateY(radians(frameCount)); rotateZ(radians(frameCount/3)); if (mousePressed) { dx = (int)map(mouseX, 0, width, 1, 50); dy = (int)map(mouseY, 0, height, 1, 50); } xy.torus(height/3, height/6, dx, dy); popMatrix(); xy.buildWaves(); // build audio from shapes xy.drawAll(); // draw all analytics } ================================================ FILE: examples/3_typography/hershey_fonts/hershey_fonts.pde ================================================ /* text_hershey_fonts draw single-line text with hershey fonts! lots of functions to learn from below big thanks for hershey font lib: https://github.com/ixd-hof/HersheyFont cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance String txt = "XYscope"; void setup() { size(512, 512, P3D); xy = new XYscope(this); // list all available fonts println("Available Hershey Fonts:\n" + join(xy.fonts, ", ")); // list available fonts // set random font //xy.textFont(xy.fonts[floor(random(xy.fonts.length))]); } void draw() { background(0); xy.clearWaves(); xy.steps(50); // set segment multiplier // draw text xy.textSize(50); xy.textAlign(CENTER, CENTER); xy.text(txt, width/2, height*.25); // draw text per character float xoff = -xy.textWidth(txt)/2.5; for (int i=0; i 0) { txtString = txtString.substring(0, txtString.length()-1); } } } else if (keyCode == 10) { txtString += "\r"; } 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) { if (xy.textWidth(txtString + key) > width) { String[] t = split(txtString, " "); if (key == ' ') { txtString += "\r"; return; } else if (t.length == 1) { txtString += "\r"; } else { txtString = txtString.replaceAll(" (?=[^ ]*$)", "\n"); } } txtString += key+""; } // change freq of add-synth if (keyCode == 17) { xy2.freq(floor(random(25))*5+.5); println(xy2.freq().x); } if (keyCode == 16) { shifted = true; } } void keyReleased() { if (keyCode == 16) { shifted = false; } } void mouseDragged() { // draw add-synth shape xy2.line(mouseX, mouseY, pmouseX, pmouseY); } void mousePressed() { // double-clicker if (millis()- doubleClicker < 500) { xy2.clearWaves(); } doubleClicker = millis(); } ================================================ FILE: examples/4_inputs/fonts/fonts.pde ================================================ /* fonts Let's draw type on the scope! ANYKEY - type out DELETE - clear text » Requires Geomerative library cc teddavis.org 2017 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // geomerative is required to generate text points import geomerative.*; RShape grp; RPoint[][] pointPaths; // store our text to draw String txtString = "XYscope"; String fontName = "FreeSans.ttf"; void setup() { size(512, 512); // initialize XYscope with default/custom sound out xy = new XYscope(this, ""); // initialize Geomerative RG.init(this); } void draw() { background(0); // clear waves like refreshing background xy.clearWaves(); // render type with Geomerative grp = RG.getText(txtString, fontName, width/2, CENTER); grp.centerIn(g, 30); RG.setPolygonizer(RG.UNIFORMSTEP); RG.setPolygonizerStep(10); pointPaths = grp.getPointsInPaths(); pushMatrix(); translate(width/2, height/2); if (pointPaths != null) { // only draw if we have points for (int i = 0; i < pointPaths.length; i++) { xy.beginShape(); for (int j=0; j < pointPaths[i].length; j++) { xy.vertex(pointPaths[i][j].x, pointPaths[i][j].y); } xy.endShape(); } } popMatrix(); // build audio from shapes xy.buildWaves(); // draw Wave + XY analytics xy.drawWave(); xy.drawXY(); } void keyPressed() { if (keyCode == 8) { xy.clearWaves(); txtString = ""; } else if (keyCode != 16 && keyCode != 17 && keyCode != 18 && keyCode != 157 && keyCode != 37 && keyCode != 38 && keyCode != 39 && keyCode != 40) { txtString += key+""; } } ================================================ FILE: examples/4_inputs/kinect/kinect.pde ================================================ /* kinect Turn the kinect depth image into a nice silhouette! » Requires OpenCV for Processing + openkinect libraries cc teddavis.org 2017 */ /* PREFS */ int kinectVer = 1; // 1 = xbox 360 vs 2 = xbox one import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // libs required for point sorting (efficient drawing) import java.util.Collections; import java.util.Comparator; // libs + settings for kinect import org.openkinect.freenect.*; import org.openkinect.processing.*; Kinect kinect; Kinect2 kinect2; // Depth image PImage depthImg; // Which pixels do we care about? int minDepth = 60; int maxDepth = 960; // What is the kinect's angle float angle; //opencv + settings import gab.opencv.*; import java.awt.*; OpenCV opencv; ArrayList contours; int thres = 30; int cutoff = 100; void setup() { size(512, 512); // initialize XYscope with default/custom sound out xy = new XYscope(this, ""); // pick kinect version (1 works great) if (kinectVer == 1) { kinect = new Kinect(this); kinect.initDepth(); angle = kinect.getTilt(); depthImg = new PImage(kinect.width, kinect.height); } else if (kinectVer == 2) { kinect2 = new Kinect2(this); kinect2.initDepth(); kinect2.initDevice(); depthImg = new PImage(kinect2.depthWidth, kinect2.depthHeight); } // initialize OpenCV (used to convert webcam to single line) opencv = new OpenCV(this, depthImg.width, depthImg.height); } void draw() { background(0); // clear waves like refreshing background xy.clearWaves(); // kinect depth data int[] rawDepth = {}; if (kinectVer == 1) { rawDepth = kinect.getRawDepth(); } else if (kinectVer == 2) { rawDepth = kinect2.getRawDepth(); } // create b/w threshold image from depth data for (int i=0; i < rawDepth.length; i++) { if (rawDepth[i] >= minDepth && rawDepth[i] <= maxDepth) { depthImg.pixels[i] = color(255); } else { depthImg.pixels[i] = color(0); } } // draw the thresholded image depthImg.updatePixels(); //image(depthImg, 0, 0); // process threshold to single line opencv.loadImage(depthImg); opencv.dilate(); opencv.flip(OpenCV.HORIZONTAL); contours = opencv.findContours(true, false); // sort group of lines for effeciant drawing Collections.sort(contours, new MyComparator()); // draw shapes on scope for (Contour contour : contours) { if (contours.size() > 0) { contour.setPolygonApproximationFactor(1); xy.beginShape(); for (PVector point : contour.getPolygonApproximation().getPoints()) { xy.vertex(point.x, point.y); } xy.endShape(); } } // build audio from shapes if(contours.size() > 0) xy.buildWaves(); // draw Path + XY analytics xy.drawXY(); xy.drawPath(); } // used for sorting points class MyComparator implements Comparator { @Override public int compare(Contour o1, Contour o2) { if (o1.numPoints() > o2.numPoints()) { return -1; } else if (o1.numPoints() < o2.numPoints()) { return 1; } return 0; } } ================================================ FILE: examples/4_inputs/obj/obj.pde ================================================ /* obj Import and render 3D obj files! cc teddavis.org 2018 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // obj file instance PShape obj; public void setup() { size(512, 512, P3D); // load 3D obj file obj = loadShape("teapot.obj"); // initialize XYscope xy = new XYscope(this); } public void draw() { background(0); // modulate freq to make beats out of shapes!? if(mousePressed){ xy.freq(map(mouseX, 0, width, 0, 50)); } // clear XYscope waves xy.clearWaves(); pushMatrix(); translate(width/2, height/2); scale(2); rotateZ(PI); rotateY(radians(frameCount)); // break 3D obj into its pieces int children = obj.getChildCount(); // render vertices in XYscope xy.beginShape(); for (int i = 0; i < children; i++) { PShape child = obj.getChild(i); int total = child.getVertexCount(); for (int j = 0; j < total; j++) { PVector v = child.getVertex(j); xy.vertex(v.x, v.y, v.z); } } xy.endShape(); // generate waveform xy.buildWaves(); popMatrix(); // draw waveform + xy simulation xy.drawWaveform(); xy.drawXY(); } ================================================ FILE: examples/4_inputs/svg/svg.pde ================================================ /* svg import any existing vector graphic as an SVG! » Requires Geomerative library cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // geomerative is required to generate svg points import geomerative.*; RShape svg; RPoint[][] pointPaths; void setup() { size(512, 512, P3D); // initialize XYscope [with default/custom sound out] xy = new XYscope(this, ""); // initialize Geomerative RG.init(this); svg = RG.loadShape("oscilloscope.svg"); svg.centerIn(g, 30); pointPaths = svg.getPointsInPaths(); } void draw() { background(0); xy.clearWaves(); // clear waves like refreshing background pushMatrix(); translate(width/2, height/2); rotateY(radians(frameCount/2)); if (pointPaths != null) { // only draw if we have points for (int i = 0; i < pointPaths.length; i++) { pushMatrix(); translate(0, 0, sin(i*5 + frameCount*.02)*100); xy.beginShape(); for (int j=0; j < pointPaths[i].length; j++) { xy.vertex(pointPaths[i][j].x, pointPaths[i][j].y); } xy.endShape(); popMatrix(); } } popMatrix(); xy.buildWaves(); // build audio from shapes // draw Wave + XY analytics xy.drawWave(); xy.drawXY(); } ================================================ FILE: examples/4_inputs/syphon/syphon.pde ================================================ /* syphon Images from any syphon source on the scope! mouseX - threshold mouseY - threshold distance » Requires OpenCV for Processing + Syphon libraries cc teddavis.org + jankenpopp.com 2017 */ //PREFS int threshold = 138; float thresholdDist = 43; int cutoff = 91; // limit min size contour import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // syphon is required for passing imagery import codeanticode.syphon.*; SyphonClient client; PImage imgs; // syphon client PImage imgo; // syphon resize PImage imgf; // syphon threshold // libs required for point sorting (efficient drawing) import java.util.Collections; import java.util.Comparator; //opencv import gab.opencv.*; import java.awt.*; OpenCV opencv; ArrayList contours; void setup() { size(512, 512, P3D); // initialize XYscope with default/custom sound out xy = new XYscope(this, ""); // initialize syphon client client = new SyphonClient(this); // initialize OpenCV (used to convert syphon to single line) opencv = new OpenCV(this, width, height); } void draw() { background(0); // only up date when sypon sends new image if (client.newFrame()) { imgs = client.getImage(imgs); } if (imgs != null) { imgf = imgs.get(); if (imgf.width > imgf.height) { imgf.resize(width, 0); } else { imgf.resize(0, height); } imgo = imgf.get(); // clear waves like refreshing background xy.clearWaves(); // adjust threshold of image for selective lines if (mousePressed) { threshold = floor(map(mouseX, 0, width, 0, 255)); thresholdDist = map(mouseY, 0, height, 0, 255-threshold); // replace variable defaults at top if you find better ones println("threshold: "+threshold +" / thresholdDist: "+ thresholdDist); } // convert syphon to high contrast threshold for (int i=0; i threshold && brightness(imgf.pixels[i]) < threshold+thresholdDist) { imgf.pixels[i] = color(200); // White } else { imgf.pixels[i] = color(0); // Black } } // process threshold to single line opencv.loadImage(imgf); opencv.dilate(); opencv.erode(); // display syphon original, threshold, opencv images – w/ keys 1, 2, 3 if (keyPressed) { if (key == '1') image(imgo, 0, 0); if (key == '2') image(imgf, 0, 0); if (key == '3') { PImage otemp = opencv.getSnapshot(); image(otemp, 0, 0); } } contours = opencv.findContours(true, false); // sort group of lines for effeciant drawing Collections.sort(contours, new MyComparator()); // draw shapes on scope for (Contour contour : contours) { if (contours.size() > 0) { contour.setPolygonApproximationFactor(1); if (contour.numPoints() > cutoff) { xy.beginShape(); for (PVector point : contour.getPolygonApproximation().getPoints()) { xy.vertex(point.x, point.y); } xy.endShape(); } } } // build audio from shapes xy.buildWaves(); } // draw XY analytics xy.drawXY(); } // used for sorting points class MyComparator implements Comparator { @Override public int compare(Contour o1, Contour o2) { if (o1.numPoints() > o2.numPoints()) { return -1; } else if (o1.numPoints() < o2.numPoints()) { return 1; } return 0; } } ================================================ FILE: examples/4_inputs/video/video.pde ================================================ /* video Import and render videos in XYscope mouseX - threshold mouseY - threshold distance keys 1, 2, 3 - view original, threshold, opencv image ** some strange behavior with Processing 4+... » Requires OpenCV for Processing + Video libraries cc teddavis.org 2018 */ //PREFS String moviePath = "BigBuckBunny_320x180_trim.mp4"; int threshold = 108; float thresholdDist = 80; int cutoff = 91; // limit min size contour import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // video library and instance import processing.video.*; Movie movie; // OpenCV imagesteps PImage imgResize; // movie resize PImage imgThres; // movie threshold // libs required for point sorting (efficient drawing) import java.util.Collections; import java.util.Comparator; //opencv import gab.opencv.*; import java.awt.*; OpenCV opencv; ArrayList contours; void setup() { size(512, 512); // initialize XYscope with default/custom sound out xy = new XYscope(this, ""); // initialize video movie = new Movie(this, moviePath); movie.play(); movie.loop(); movie.volume(0); // initialize OpenCV (used to convert syphon to single line) opencv = new OpenCV(this, width, height); } void draw() { background(0); if (movie.available() == true) { movie.read(); } if (movie != null) { // clear waves like refreshing background xy.clearWaves(); // resize movie to fit canvas imgResize = movie.get(); if (imgResize.width > imgResize.height) { imgResize.resize(width, 0); } else { imgResize.resize(0, height); } imgThres = imgResize.get(); // adjust threshold of image for selective lines if (mousePressed) { threshold = floor(map(mouseX, 0, width, 0, 255)); thresholdDist = map(mouseY, 0, height, 0, 255-threshold); // replace variable defaults at top if video needs specific ones println("threshold: "+threshold +" / thresholdDist: "+ thresholdDist); } // convert video to high contrast threshold for (int i=0; i threshold && brightness(imgThres.pixels[i]) < threshold+thresholdDist) { imgThres.pixels[i] = color(255); // White } else { imgThres.pixels[i] = color(0); // Black } } // process threshold to single line opencv.loadImage(imgThres); opencv.dilate(); opencv.erode(); contours = opencv.findContours(true, false); // display video original, threshold, opencv images – w/ keys 1, 2, 3 if (keyPressed) { if (key == '1') image(imgResize, 0, 0); if (key == '2') image(imgThres, 0, 0); if (key == '3') { PImage otemp = opencv.getSnapshot(); image(otemp, 0, 0); } } // sort group of lines for effeciant drawing Collections.sort(contours, new MyComparator()); // draw shapes on scope for (Contour contour : contours) { if (contours.size() > 0) { contour.setPolygonApproximationFactor(1); if (contour.numPoints() > cutoff) { xy.beginShape(); for (PVector point : contour.getPolygonApproximation().getPoints()) { xy.vertex(point.x, point.y); } xy.endShape(); } } } // build audio from shapes xy.buildWaves(); } // draw XY analytics xy.drawXY(); } // used for sorting points class MyComparator implements Comparator { @Override public int compare(Contour o1, Contour o2) { if (o1.numPoints() > o2.numPoints()) { return -1; } else if (o1.numPoints() < o2.numPoints()) { return 1; } return 0; } } ================================================ FILE: examples/4_inputs/webcam/webcam.pde ================================================ /* webcam You via your webcam on the scope! mouseX - threshold mouseY - threshold distance » Requires OpenCV for Processing + Video libraries cc teddavis.org 2017 – 2023 */ //PREFS int threshold = 65; float thresholdDist = 115; import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // video is required for webcam import processing.video.*; Capture video; // libs required for point sorting (efficient drawing) import java.util.Collections; import java.util.Comparator; //opencv import gab.opencv.*; import java.awt.*; OpenCV opencv; ArrayList contours; int cutoff = 91; PImage p; void setup() { size(512, 512, P3D); // initialize XYscope with default/custom sound out xy = new XYscope(this, ""); // initialize video capture video = new Capture(this, 640, 480); video.start(); p = new PImage(video.width, video.height); // initialize OpenCV (used to convert webcam to single line) opencv = new OpenCV(this, p.width, p.height); } void draw() { background(0); if (video.available()) { video.read(); } // clear waves like refreshing background xy.clearWaves(); // adjust threshold of image for selective lines if (mousePressed) { threshold = floor(map(mouseX, 0, width, 0, 255)); thresholdDist = map(mouseY, 0, height, 0, 255-threshold); // replace variable defaults at top if you find better ones println("threshold: "+threshold +" / thresholdDist: "+ thresholdDist); } // convert video to high contrast threshold video.loadPixels(); if (video.pixels.length == 0) { return; } for (int i=0; i threshold && brightness(video.pixels[i]) < threshold+thresholdDist) { p.pixels[i] = color(0); // White } else { p.pixels[i] = color(255); // Black } p.updatePixels(); } // process threshold to single line opencv.loadImage(p); opencv.flip(OpenCV.HORIZONTAL); opencv.dilate(); contours = opencv.findContours(true, false); // display video original, threshold, opencv images – w/ keys 1, 2, 3 if (keyPressed) { pushMatrix(); if (key == '1') { scale(-1, 1); image(video, -video.width, 0); } if (key == '2') { scale(-1, 1); image(p, -p.width, 0); } if (key == '3') { PImage otemp = opencv.getSnapshot(); image(otemp, 0, 0); } popMatrix(); } // sort group of lines for effeciant drawing Collections.sort(contours, new MyComparator()); // draw shapes on scope for (Contour contour : contours) { if (contours.size() > 0) { contour.setPolygonApproximationFactor(1); if (contour.numPoints() > cutoff) { xy.beginShape(); for (PVector point : contour.getPolygonApproximation().getPoints()) { xy.vertex(point.x, point.y); } xy.endShape(); } } } // build audio from shapes xy.buildWaves(); // draw XY analytics xy.drawXY(); } // used for sorting points class MyComparator implements Comparator { @Override public int compare(Contour o1, Contour o2) { if (o1.numPoints() > o2.numPoints()) { return -1; } else if (o1.numPoints() < o2.numPoints()) { return 1; } return 0; } } ================================================ FILE: examples/4_inputs/webcam_processing4/webcam_processing4.pde ================================================ /* webcam processing4 You via your webcam on the scope! mouseX - threshold mouseY - threshold distance * Updated version solving hiccups with Processing4 + Apple Silicon » Requires 'OpenCV for Processing' + 'Video Library for Processing 4' libraries cc teddavis.org 2017 – 23 */ //PREFS int threshold = 65; float thresholdDist = 115; import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // video is required for webcam import processing.video.*; Capture video; // libs required for point sorting (efficient drawing) import java.util.Collections; import java.util.Comparator; //opencv import gab.opencv.*; import java.awt.*; OpenCV opencv; ArrayList contours; int cutoff = 91; PImage p; void setup() { size(512, 512, P3D); // initialize XYscope with default/custom sound out xy = new XYscope(this, ""); xy.limitPath(2); // remove box around whole thing // initialize video capture video = new Capture(this, 640, 480); video.start(); image(video, 0, 0); p = new PImage(video.width, video.height); // initialize OpenCV (used to convert webcam to single line) opencv = new OpenCV(this, p.width, p.height); } void draw() { background(0); if (video.available()) { video.read(); } // clear waves like refreshing background xy.clearWaves(); // adjust threshold of image for selective lines if (mousePressed) { threshold = floor(map(mouseX, 0, width, 0, 255)); thresholdDist = map(mouseY, 0, height, 0, 255-threshold); // replace variable defaults at top if you find better ones println("threshold: "+threshold +" / thresholdDist: "+ thresholdDist); } // convert video to high contrast threshold video.loadPixels(); if (video.pixels.length == 0) { return; } for (int i=0; i threshold && brightness(video.pixels[i]) < threshold+thresholdDist) { p.pixels[i] = color(0); // White } else { p.pixels[i] = color(255); // Black } } p.updatePixels(); // process threshold to single line opencv.loadImage(p); opencv.flip(OpenCV.HORIZONTAL); opencv.dilate(); contours = opencv.findContours(true, false); // display video original, threshold, opencv images – w/ keys 1, 2, 3 if (keyPressed) { pushMatrix(); if (key == '1') { scale(-1, 1); image(video, -video.width, 0); } if (key == '2') { scale(-1, 1); image(p, -p.width, 0); } if (key == '3') { PImage otemp = opencv.getSnapshot(); image(otemp, 0, 0); } popMatrix(); } // sort group of lines for effeciant drawing Collections.sort(contours, new MyComparator()); // draw shapes on scope for (Contour contour : contours) { if (contours.size() > 0) { contour.setPolygonApproximationFactor(1); if (contour.numPoints() > cutoff) { xy.beginShape(); for (PVector point : contour.getPolygonApproximation().getPoints()) { xy.vertex(point.x, point.y); } xy.endShape(); } } } // build audio from shapes xy.buildWaves(); // draw XY analytics xy.drawXY(); } // used for sorting points class MyComparator implements Comparator { @Override public int compare(Contour o1, Contour o2) { if (o1.numPoints() > o2.numPoints()) { return -1; } else if (o1.numPoints() < o2.numPoints()) { return 1; } return 0; } } ================================================ FILE: examples/5_displays/laser/laser.pde ================================================ /* laser Guide to driving RGB laser with XYscope. cc teddavis.org 2018 – 23 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance void setup() { size(640, 480, P3D); background(0); // initialize XYscope with default/custom sound out xy = new XYscope(this, "MOTU 1-2"); // only stereo pairs allowed in processing, so it's broken to R, GB // incase 2nd channel of R pair is useful for blanking/etc. xy.laser("MOTU 3-4", "MOTU 5-6"); //xy.laser(mixerR, mixerGB); // RGB waveforms have own freq, so we can them out of sync xy.strokeFreq(50.05, 50.1, 50.2); } void draw() { background(0); // clear waves like refreshing background xy.clearWaves(); xy.limitPath(0); // avoid drawing any forms beyond view xy.strokeDash(8); // optional dashes in the RGB stroke if (mousePressed) { // set low-pass filter of laser float newLPF = map(mouseX, 0, width, 1, 10000); xy.laserLPF(newLPF); // limit number of points it can draw with xy.limitPoints(floor(map(mouseY, 0, height, 0, 100))); } pushMatrix(); translate(width/2, height/2); rotateY(radians(frameCount*.2)); float s = height*.25; xy.stroke(0, 255, 255); // set RGB stroke before shapes (0-255) xy.ellipse(-s, 0, s, s); // rotating for infinity movement rotateY(radians(frameCount*.2)); xy.stroke(255, 0, 255); rotate(radians(180)); // match ellipse startings points rotateX(radians(180)); // match ellipse startings points xy.ellipse(-s, 0, s, s); popMatrix(); // build audio from shapes xy.buildWaves(); // draw RGB Waveforms + XY simulation xy.drawRGB(); //xy.drawWaveform(); xy.drawXY(); } ================================================ FILE: examples/5_displays/vectrex/vectrex.pde ================================================ /* vectrex Basics settings for using a modded Vectrex monitor. cc teddavis.org 2018 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance void setup() { size(512, 512); // initialize XYscope with default sound out xy = new XYscope(this, ""); // set XYscope into Vectrex aspect-ratio mode xy.vectrex(90); // 90 for landscape, 0 for portrait /* If the SPOT-KILLER MOD was applied (z/brightness is always on), this auto sets the brightness (from way turned down) when the sketch runs. */ //xy.z("MOTU 3-4"); // use custom 3rd channel audio device //xy.zRange(.5, 0); } void draw() { background(0); // clear waves like refreshing background xy.clearWaves(); // set detail of vertex ellipse xy.ellipseDetail(30); // draw two primative shapes, testing boundry of screen xy.rect(0, 0, width, height); float s = map(mouseX, 0, width, -height/2, height/2); xy.ellipse(width/2, height/2, s, s); // build audio from shapes xy.buildWaves(); // draw all analytics xy.drawAll(); } ================================================ FILE: examples/6_outputs/audio_recorder/audio_recorder.pde ================================================ /* audio recorder Export your XYscope as .wav audio files! just use: xy.recorderBegin(filename) + xy.recorderEnd() cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance void setup() { size(512, 512, P3D); // declares size of output window xy = new XYscope(this, 48000); // use desired sampleRate //xy.getMixerInfo(); // lists all audio devices } void draw() { background(0); xy.clearWaves(); push(); translate(width/2, height/2); rotateY(radians(frameCount/2)); rotateZ(radians(frameCount/2)); xy.box(height/2); float off = width/5; translate(random(-off, off), random(-off, off), random(-off, off)); xy.sphere(random(50)); pop(); xy.buildWaves(); xy.drawAll(); } void keyPressed() { if (key == 'r') { if (xy.recorder != null && xy.recorder.isRecording()) { xy.recorderEnd(); } else { xy.recorderBegin("box-spheres"); // leave blank for "XYscope" } } } ================================================ FILE: examples/6_outputs/osc_wavetables/osc_wavetables.pde ================================================ /* osc wavetables transport your XYscope custom wavetables via OSC to other tools to visual forms drawn, add effects, pass into custom oscillators. format: [x1, y1, z1], [x2, y2, z2], ...]; + import /data/P5L_xyscope-osc_wavetables.json to offline P5LIVE for demo * not working for your tool of choice? join discussion: https://github.com/ffd8/xyscope/issues/5 cc teddavis.org 2023 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance import java.util.Arrays; // needed for 2D Array to string // OSC code import oscP5.*; import netP5.*; OscP5 oscP5; NetAddress myRemoteLocation; float fc = 0; void setup() { size(512, 512, P3D); // declares size of output window xy = new XYscope(this); // define XYscope instance //xy.getMixerInfo(); // lists all audio devices /* start oscP5, sending outgoing messages */ oscP5 = new OscP5(this, 12001); // receiving port myRemoteLocation = new NetAddress("127.0.0.1", 12000); // sending port } void draw() { background(0); xy.clearWaves(); xy.limitPath(0); xy.steps(20); pushMatrix(); translate(width/2, height/2); rotateX(radians(-frameCount)); rotateY(radians(-frameCount/2)); xy.textSize(72); xy.textAlign(CENTER, CENTER); xy.text("XYscope", 0, 0); xy.textSize(32); float tw = xy.textWidth("XYscope"); fc += mouseX; for (int i=0; i<3; i++) { pushMatrix(); rotateX(radians(-fc+i*50)); translate(map(i, 0, 3, -tw, tw), -height*.1, height*.1); xy.text("OSC", 0, 0); popMatrix(); } popMatrix(); xy.buildWaves(); xy.drawAll(); sendWaves(); // send OSC message of waves [x1, y1, z1], [x2, y2, z2], ...]; } void sendWaves() { float[][] getWaves = new float[xy.tableX.size()][3]; for (int i=0; i < xy.tableX.size(); i++) { getWaves[i][0] = xy.tableX.get(i); getWaves[i][1] = xy.tableY.get(i); } //System.out.println(Arrays.deepToString(getWaves)); String xyWavetables = Arrays.deepToString(getWaves); OscMessage myMessage = new OscMessage("/XYscope"); myMessage.add(xyWavetables); /* add an int to the osc message */ // /* send the message */ oscP5.send(myMessage, myRemoteLocation); } void keyPressed() { // optionally only send on demand, disable within draw() if (keyCode == 32) { sendWaves(); } } ================================================ FILE: examples/7_custom_waves/customWaves_drawingXYZ/customWaves_drawingXYZ.pde ================================================ /* customWaves_drawing Test out our sound cards by manually drawing the wave form. Very useful for testing just the z-axis blanking parallel to another sketch DELETE - resets waveforms cc teddavis.org 2017 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance // setup local waveforms float[] x, y, z; int dragDist = 14; int dragRegion = 0; void setup() { size(400, 600, P3D); background(0); // initialize XYscope with custom outXY // left blank so it runs, but fill in with your audio card xy = new XYscope(this, ""); // custom z xy.z(""); // initialize waves resetWaves(); } void draw() { background(0); // display text and color boxes displayInfo(); // build each wave custom from our local waveform xy.buildX(x); xy.buildY(y); xy.buildZ(z); // draw waveform analytics xy.drawWaveform(); } void mousePressed() { // only draw in region clicked on if (mouseY > height*.125 && mouseY < height*.375) { dragRegion = 1; } else if (mouseY > height*.375 && mouseY < height*.625) { dragRegion = 2; } else if (mouseY > height*.625 && mouseY < height*.875) { dragRegion = 3; } } void mouseReleased() { // release custom region dragRegion = 0; } void mouseDragged() { // modify waveform based on drawing for (int i=0; i < x.length; i++) { int mappedX = floor(map(mouseX, 0, width, 0, x.length)); if (abs(i-mappedX) < dragDist) { if (mouseY > height*.125 && mouseY < height*.375 && dragRegion == 1) { float mappedY = map(mouseY, height*.375, height*.125, 0, 1); x[i] = mappedY; } else if (mouseY > height*.375 && mouseY < height*.625 && dragRegion == 2) { float mappedY = map(mouseY, height*.375, height*.625, 0, 1); z[i] = mappedY; } else if (mouseY > height*.625 && mouseY < height*.875 && dragRegion == 3) { float mappedY = map(mouseY, height*.625, height*.875, 0, 1); y[i] = mappedY; } } } } void keyPressed() { // reset waves on DELETE if (keyCode == 8) { // DELETE resetWaves(); } } void resetWaves() { x = new float[xy.waveSize()]; y = new float[xy.waveSize()]; z = new float[xy.waveSize()]; for (int i=0; i < x.length; i++) { x[i] = .5; y[i] = .5; z[i] = .5; } } void displayInfo() { noStroke(); fill(255, 0, 0, 50); rect(0, height*.125, width, height*.25); fill(0, 255, 0, 50); rect(0, height*.375, width, height*.25); fill(0, 0, 255, 50); rect(0, height*.625, width, height*.25); fill(255, 0, 0); text("X - LEFT/HORIZONTAL", 5, height*.125, width, 100); fill(0, 255, 0); text("Z - BLANKING", 5, height*.375, width, 100); fill(0, 0, 255); text("Y - RIGHT/VERTICAL", 5, height*.625, width, 100); } ================================================ FILE: examples/7_custom_waves/customWaves_noiseXY/customWaves_noiseXY.pde ================================================ /* customWaves_buildXY You don't have to use primitive forms, you can also generate the waveforms by your own code and logic. cc teddavis.org 2017-23 */ import ddf.minim.*; // minim req to gen audio import xyscope.*; // import XYscope XYscope xy; // create XYscope instance float[] yWave, xWave; void setup() { size(512, 512); background(0); // initialize custom local minim xy = new XYscope(this); // function below to build waves genNoiseWave(); } void draw() { background(0); // draw analytics xy.drawAll(); } void keyPressed() { // refresh noise with spacebar if (keyCode == 32) { genNoiseWave(); } } void genNoiseWave() { // set new noiseSeed noiseSeed(frameCount); // get bufferSize() of output println(xy.waveSize()); // initialize array for storing values yWave = new float[xy.waveSize()]; xWave = new float[xy.waveSize()]; float nx = random(1); float ny = random(1); // add noise walker to waveform for (int i=0; i width) { x = -s; v = random(1, 10); } xy.rect(x, y, s, s); // build audio from shapes xy.buildWaves(); // draw just two analytics xy.drawWave(); xy.drawXY(); } // process keyPressed void checkKey(int keyC) { if (keyC == 38) { // UP xy.freq(xy.freq().x+.5); } else if (keyC == 40) { // DOWN xy.freq(xy.freq().x-.5); } } } ================================================ FILE: license.txt ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: resources/README.md ================================================ ## How to install ##library.name## ### Install with the Contribution Manager Add 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. Not 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. ### Manual Install Contributed 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. By default the following locations are used for your sketchbook folder: * For Mac users, the sketchbook folder is located inside `~/Documents/Processing` * For Windows users, the sketchbook folder is located inside `My Documents/Processing` Download ##library.name## from ##library.url## Unzip 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. The folder structure for Library ##library.name## should be as follows: ``` Processing libraries ##library.name## examples library ##library.name##.jar reference src ``` Some folders like `examples` or `src` might be missing. After Library ##library.name## has been successfully installed, restart the Processing application. ### Troubleshooting If 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##). ================================================ FILE: resources/build.properties ================================================ # Create a Library for the Processing open source programming language and # environment (http://processing.org/) # # Customize the build properties to make the ant-build-process work for your # environment. How? Please read the comments below. # # The default properties are set for OS X. Please refer to comments for Windows # settings. # Where is your Processing sketchbook located? # If you are not sure, check the sketchbook location in your Processing # application preferences. # ${user.home} points the compiler to your home directory. # For windows the default path to your sketchbook would be # ${user.home}/My Documents/Processing (make adjustments below) #sketchbook.location=${user.home}/My Documents/Processing sketchbook.location=${user.home}/Documents/Processing # Where are the jar files located that are required for compiling your Library # such as e.g. core.jar? # By default the local classpath location points to folder libs inside Eclipse's # workspace (by default found in your home directory). # For Windows, the default path would be # ${user.home}/Documents/workspace/libs (make adjustments below) # For OS X,the following path will direct you into Processing's application # package, in case you put Processing inside your Applications folder. classpath.local.location=lib #classpath.local.location=/Applications/Processing_3_3_2.app/Contents/Java/core/library #${user.home}/Documents/workspace/ # Add all jar files that are required for compiling your project to the local # and project classpath. Use a comma as delimiter. These jar files must be # inside your classpath.local.location folder. classpath.local.include=core.jar # Add Processing's libraries folder to the classpath. # If you don't need to include the libraries folder to your classpath, comment # out the following line. #classpath.libraries.location=${sketchbook.location}/libraries classpath.libraries.location= # Set the java version that should be used to compile your Library. java.target.version=17 # Set the description of the Ant build.xml file. ant.description=Processing Library Ant build file. # Give your Library a name. The name must not contain spaces or special # characters. project.name=XYscope # The name as the user will see it. This can contain spaces and special # characters. project.prettyName=XYscope # Use 'normal' or 'fast' as value for project.compile. # 'fast' will only compile the project into your sketchbook. # 'normal' will compile the distribution including the javadoc-reference and all # web-files (the compile process here takes longer). # All files compiled with project.compile=normal are stored in the distribution # folder. project.compile=normal # Set your name and URL, used for the web page and properties file. author.name=Ted Davis author.url=https://www.teddavis.org # Set the web page for your Library. # This is NOT a direct link to where to download it. library.url=https://teddavis.org/xyscope # Set the category (or categories) of your Library from the following list: # "3D" "Animation" "Compilations" "Data" # "Fabrication" "Geometry" "GUI" "Hardware" # "I/O" "Language" "Math" "Simulation" # "Sound" "Utilities" "Typography" "Video & Vision" # # If a value other than those listed is used, your Library will listed as # "Other". Many categories must be comma-separated. library.categories=Animation, Sound, Hardware # A short sentence (or fragment) to summarize the Library's function. This will # be shown from inside the PDE when the Library is being installed. Avoid # repeating the name of your Library here. Also, avoid saying anything redundant # like mentioning that it's a Library. This should start with a capitalized # letter, and end with a period. library.sentence= XYScope is a library for Processing to render graphics on a vector display (oscilloscope, laser) by converting them to audio. # Additional information suitable for the Processing website. The value of # 'sentence' always will be prepended, so you should start by writing the # second sentence here. If your Library only works on certain operating systems, # mention it here. library.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!). library.acknowledgements= Acknowledgements:
Just Van Rossum for the enlightening conversation on my X-Y attempts.
Stefanie Bruer for feeding the obsession with crucial theory + context.
Hansi Raber for Java meta insights + finding external WaveTable bug! # Set the source code repository for your project. # We recommend Bitbucket (https://bitbucket.org) or GitHub (https://github.com). source.host=GitHub source.url=https://github.com/ffd8/xyscope source.repository=https://github.com/ffd8/xyscope.git # The current version of your Library. # This number must be parsable as an int. It increments once with each release. # This is used to compare different versions of the same Library, and check if # an update is available. library.version=5 # The version as the user will see it. library.prettyVersion=3.0.0 # The min and max revision of Processing compatible with your Library. # Note that these fields use the revision and not the version of Processing, # parsable as an int. For example, the revision number for 2.2.1 is 227. # You can find the revision numbers in the change log: https://raw.githubusercontent.com/processing/processing/master/build/shared/revisions.txt # Only use maxRevision (or minRevision), when your Library is known to # break in a later (or earlier) release. Otherwise, use the default value 0. compatible.minRevision=0 compatible.maxRevision=0 # The platforms and Processing version that the Library has been tested # against. This information is only used in the generated webpage. tested.platform=MacOS, Windows, Linux tested.processingVersion=3.3.7 # Additional information for the generated webpage. library.copyright=(cc) 2017-23 library.dependencies=Minim + (Geomerative, OpenCV, openkinect, Video for xtra_ examples) library.keywords=oscilloscope, x-y mode, laser, vectrex, vector, vector display, vector monitor, vector graphics # Include javadoc references into your project's javadocs. #javadoc.java.href=http://docs.oracle.com/javase/7/docs/api/ javadoc.java.href=http://docs.oracle.com/javase/8/docs/api/ javadoc.processing.href=http://processing.github.io/processing-javadocs/core/ ================================================ FILE: resources/build.xml ================================================ ${ant.description} ${line} Building the Processing Library ${project.name} ${library.version} ${line} src path ${project.src} bin path ${project.bin} classpath.local ${classpath.local.location} sketchbook ${sketchbook.location} java version ${java.target.version} ${line} ${exampleDir} ${line} Name ${project.name} Version ${library.prettyVersion} (${library.version}) Compiled ${project.compile} Sketchbook ${sketchbook.location} ${line} done, finished. ${line} ================================================ FILE: resources/code/ExampleTaglet.java ================================================ /* * Copyright 2002 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * -Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * -Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE SOFTWARE OR * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF * THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that Software is not designed, licensed or * intended for use in the design, construction, operation or * maintenance of any nuclear facility. */ import com.sun.tools.doclets.Taglet; import com.sun.javadoc.*; import java.util.Map; import java.io.*; /** * A sample Taglet representing @example. This tag can be used in any kind of * {@link com.sun.javadoc.Doc}. It is not an inline tag. The text is displayed * in yellow to remind the developer to perform a task. For * example, "@example Hello" would be shown as: *
*
* To Do: *
Fix this! *
*
* * @author Jamie Ho * @since 1.4 */ public class ExampleTaglet implements Taglet { private static final String NAME = "example"; private static final String HEADER = "example To Do:"; /** * Return the name of this custom tag. */ public String getName() { return NAME; } /** * Will return true since @example * can be used in field documentation. * @return true since @example * can be used in field documentation and false * otherwise. */ public boolean inField() { return true; } /** * Will return true since @example * can be used in constructor documentation. * @return true since @example * can be used in constructor documentation and false * otherwise. */ public boolean inConstructor() { return true; } /** * Will return true since @example * can be used in method documentation. * @return true since @example * can be used in method documentation and false * otherwise. */ public boolean inMethod() { return true; } /** * Will return true since @example * can be used in method documentation. * @return true since @example * can be used in overview documentation and false * otherwise. */ public boolean inOverview() { return true; } /** * Will return true since @example * can be used in package documentation. * @return true since @example * can be used in package documentation and false * otherwise. */ public boolean inPackage() { return true; } /** * Will return true since @example * can be used in type documentation (classes or interfaces). * @return true since @example * can be used in type documentation and false * otherwise. */ public boolean inType() { return true; } /** * Will return false since @example * is not an inline tag. * @return false since @example * is not an inline tag. */ public boolean isInlineTag() { return false; } /** * Register this Taglet. * @param tagletMap the map to register this tag to. */ public static void register(Map tagletMap) { ExampleTaglet tag = new ExampleTaglet(); Taglet t = (Taglet) tagletMap.get(tag.getName()); if (t != null) { tagletMap.remove(tag.getName()); } tagletMap.put(tag.getName(), tag); } /** * Given the Tag representation of this custom * tag, return its string representation. * @param tag the Tag representation of this custom tag. */ public String toString(Tag tag) { return createHTML(readFile(tag.text())); } /** * Given an array of Tags representing this custom * tag, return its string representation. * @param tags the array of Tags representing of this custom tag. */ public String toString(Tag[] tags) { if (tags.length == 0) { return null; } return createHTML(readFile(tags[0].text())); } String createHTML(String theString) { if(theString!=null) { String dd = ""; return dd+"\n
" + "
+Example
" + "
"+theString+"
" + "
"; } return ""; } /** * check if the examples directory exists and return the example as given in the tag. * @param theExample the name of the example */ String readFile(String theExample) { String record = ""; String myResult = ""; int recCount = 0; String myDir = "../examples"; File file=new File(myDir); if(file.exists()==false) { myDir = "./examples"; } try { FileReader fr = new FileReader(myDir+"/"+theExample+"/"+theExample+".pde"); BufferedReader br = new BufferedReader(fr); record = new String(); while ((record = br.readLine()) != null) { myResult += record+"\n"; } } catch (IOException e) { System.out.println(e); return null; } return myResult; } } ================================================ FILE: resources/code/doc.sh ================================================ # a shell script to create a java documentation # for a processing Library. # # make changes to the variables below so they # fit the structure of your Library # the package name of your Library package=template; # source folder location src=../src; # the destination folder of your documentation dest=../documentation; # compile the java documentation javadoc -d $dest -stylesheetfile ./stylesheet.css -sourcepath ${src} ${package} ================================================ FILE: resources/library.properties ================================================ # More on this file here: https://github.com/processing/processing/wiki/Library-Basics # UTF-8 supported. # The name of your Library as you want it formatted. name = ##library.name## # List of authors. Links can be provided using the syntax [author name](url). authors = [##author.name##](##author.url##) # A web page for your Library, NOT a direct link to where to download it. url = ##library.url## # The category (or categories) of your Library, must be from the following list: # "3D" "Animation" "Compilations" "Data" # "Fabrication" "Geometry" "GUI" "Hardware" # "I/O" "Language" "Math" "Simulation" # "Sound" "Utilities" "Typography" "Video & Vision" # # If a value other than those listed is used, your Library will listed as # "Other". Many categories must be comma-separated. categories = ##library.categories## # A short sentence (or fragment) to summarize the Library's function. This will # be shown from inside the PDE when the Library is being installed. Avoid # repeating the name of your Library here. Also, avoid saying anything redundant # like mentioning that it's a Library. This should start with a capitalized # letter, and end with a period. sentence = ##library.sentence## # Additional information suitable for the Processing website. The value of # 'sentence' always will be prepended, so you should start by writing the # second sentence here. If your Library only works on certain operating systems, # mention it here. paragraph = ##library.paragraph## # Links in the 'sentence' and 'paragraph' attributes can be inserted using the # same syntax as for authors. # That is, [here is a link to Processing](http://processing.org/) # A version number that increments once with each release. This is used to # compare different versions of the same Library, and check if an update is # available. You should think of it as a counter, counting the total number of # releases you've had. version = ##library.version## # This must be parsable as an int # The version as the user will see it. If blank, the version attribute will be # used here. This should be a single word, with no spaces. prettyVersion = ##library.prettyVersion## # This is treated as a String # The min and max revision of Processing compatible with your Library. # Note that these fields use the revision and not the version of Processing, # parsable as an int. For example, the revision number for 2.2.1 is 227. # You can find the revision numbers in the change log: https://raw.githubusercontent.com/processing/processing/master/build/shared/revisions.txt # Only use maxRevision (or minRevision), when your Library is known to # break in a later (or earlier) release. Otherwise, use the default value 0. minRevision = ##compatible.minRevision## maxRevision = ##compatible.maxRevision## ================================================ FILE: resources/stylesheet.css ================================================ /* Javadoc style sheet */ /* Overall document style */ @import url('resources/fonts/dejavu.css'); body { background-color:#ffffff; color:#353833; font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; font-size:14px; margin:0; } a:link, a:visited { text-decoration:none; color:#4A6782; } a:hover, a:focus { text-decoration:none; color:#bb7a2a; } a:active { text-decoration:none; color:#4A6782; } a[name] { color:#353833; } a[name]:hover { text-decoration:none; color:#353833; } pre { font-family:'DejaVu Sans Mono', monospace; font-size:14px; } h1 { font-size:20px; } h2 { font-size:18px; } h3 { font-size:16px; font-style:italic; } h4 { font-size:13px; } h5 { font-size:12px; } h6 { font-size:11px; } ul { list-style-type:disc; } code, tt { font-family:'DejaVu Sans Mono', monospace; font-size:14px; padding-top:4px; margin-top:8px; line-height:1.4em; } dt code { font-family:'DejaVu Sans Mono', monospace; font-size:14px; padding-top:4px; } table tr td dt code { font-family:'DejaVu Sans Mono', monospace; font-size:14px; vertical-align:top; padding-top:4px; } sup { font-size:8px; } /* Document title and Copyright styles */ .clear { clear:both; height:0px; overflow:hidden; } .aboutLanguage { float:right; padding:0px 21px; font-size:11px; z-index:200; margin-top:-9px; } .legalCopy { margin-left:.5em; } .bar a, .bar a:link, .bar a:visited, .bar a:active { color:#FFFFFF; text-decoration:none; } .bar a:hover, .bar a:focus { color:#bb7a2a; } .tab { background-color:#0066FF; color:#ffffff; padding:8px; width:5em; font-weight:bold; } /* Navigation bar styles */ .bar { background-color:#4D7A97; color:#FFFFFF; padding:.8em .5em .4em .8em; height:auto;/*height:1.8em;*/ font-size:11px; margin:0; } .topNav { background-color:#4D7A97; color:#FFFFFF; float:left; padding:0; width:100%; clear:right; height:2.8em; padding-top:10px; overflow:hidden; font-size:12px; } .bottomNav { margin-top:10px; background-color:#4D7A97; color:#FFFFFF; float:left; padding:0; width:100%; clear:right; height:2.8em; padding-top:10px; overflow:hidden; font-size:12px; } .subNav { background-color:#dee3e9; float:left; width:100%; overflow:hidden; font-size:12px; } .subNav div { clear:left; float:left; padding:0 0 5px 6px; text-transform:uppercase; } ul.navList, ul.subNavList { float:left; margin:0 25px 0 0; padding:0; } ul.navList li{ list-style:none; float:left; padding: 5px 6px; text-transform:uppercase; } ul.subNavList li{ list-style:none; float:left; } .topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { color:#FFFFFF; text-decoration:none; text-transform:uppercase; } .topNav a:hover, .bottomNav a:hover { text-decoration:none; color:#bb7a2a; text-transform:uppercase; } .navBarCell1Rev { background-color:#F8981D; color:#253441; margin: auto 5px; } .skipNav { position:absolute; top:auto; left:-9999px; overflow:hidden; } /* Page header and footer styles */ .header, .footer { clear:both; margin:0 20px; padding:5px 0 0 0; } .indexHeader { margin:10px; position:relative; } .indexHeader span{ margin-right:15px; } .indexHeader h1 { font-size:13px; } .title { color:#2c4557; margin:10px 0; } .subTitle { margin:5px 0 0 0; } .header ul { margin:0 0 15px 0; padding:0; } .footer ul { margin:20px 0 5px 0; } .header ul li, .footer ul li { list-style:none; font-size:13px; } /* Heading styles */ div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { background-color:#dee3e9; border:1px solid #d0d9e0; margin:0 0 6px -8px; padding:7px 5px; } ul.blockList ul.blockList ul.blockList li.blockList h3 { background-color:#dee3e9; border:1px solid #d0d9e0; margin:0 0 6px -8px; padding:7px 5px; } ul.blockList ul.blockList li.blockList h3 { padding:0; margin:15px 0; } ul.blockList li.blockList h2 { padding:0px 0 20px 0; } /* Page layout container styles */ .contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { clear:both; padding:10px 20px; position:relative; } .indexContainer { margin:10px; position:relative; font-size:12px; } .indexContainer h2 { font-size:13px; padding:0 0 3px 0; } .indexContainer ul { margin:0; padding:0; } .indexContainer ul li { list-style:none; padding-top:2px; } .contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { font-size:12px; font-weight:bold; margin:10px 0 0 0; color:#4E4E4E; } .contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { margin:5px 0 10px 0px; font-size:14px; font-family:'DejaVu Sans Mono',monospace; } .serializedFormContainer dl.nameValue dt { margin-left:1px; font-size:1.1em; display:inline; font-weight:bold; } .serializedFormContainer dl.nameValue dd { margin:0 0 0 1px; font-size:1.1em; display:inline; } /* List styles */ ul.horizontal li { display:inline; font-size:0.9em; } ul.inheritance { margin:0; padding:0; } ul.inheritance li { display:inline; list-style:none; } ul.inheritance li ul.inheritance { margin-left:15px; padding-left:15px; padding-top:1px; } ul.blockList, ul.blockListLast { margin:10px 0 10px 0; padding:0; } ul.blockList li.blockList, ul.blockListLast li.blockList { list-style:none; margin-bottom:15px; line-height:1.4; } ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { padding:0px 20px 5px 10px; border:1px solid #ededed; background-color:#f8f8f8; } ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { padding:0 0 5px 8px; background-color:#ffffff; border:none; } ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { margin-left:0; padding-left:0; padding-bottom:15px; border:none; } ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { list-style:none; border-bottom:none; padding-bottom:0; } table tr td dl, table tr td dl dt, table tr td dl dd { margin-top:0; margin-bottom:1px; } /* Table styles */ .overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary { width:100%; border-left:1px solid #EEE; border-right:1px solid #EEE; border-bottom:1px solid #EEE; } .overviewSummary, .memberSummary { padding:0px; } .overviewSummary caption, .memberSummary caption, .typeSummary caption, .useSummary caption, .constantsSummary caption, .deprecatedSummary caption { position:relative; text-align:left; background-repeat:no-repeat; color:#253441; font-weight:bold; clear:none; overflow:hidden; padding:0px; padding-top:10px; padding-left:1px; margin:0px; white-space:pre; } .overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, .useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, .overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, .useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, .overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, .useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, .overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, .useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited { color:#FFFFFF; } .overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, .useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span { white-space:nowrap; padding-top:5px; padding-left:12px; padding-right:12px; padding-bottom:7px; display:inline-block; float:left; background-color:#F8981D; border: none; height:16px; } .memberSummary caption span.activeTableTab span { white-space:nowrap; padding-top:5px; padding-left:12px; padding-right:12px; margin-right:3px; display:inline-block; float:left; background-color:#F8981D; height:16px; } .memberSummary caption span.tableTab span { white-space:nowrap; padding-top:5px; padding-left:12px; padding-right:12px; margin-right:3px; display:inline-block; float:left; background-color:#4D7A97; height:16px; } .memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab { padding-top:0px; padding-left:0px; padding-right:0px; background-image:none; float:none; display:inline; } .overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, .useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd { display:none; width:5px; position:relative; float:left; background-color:#F8981D; } .memberSummary .activeTableTab .tabEnd { display:none; width:5px; margin-right:3px; position:relative; float:left; background-color:#F8981D; } .memberSummary .tableTab .tabEnd { display:none; width:5px; margin-right:3px; position:relative; background-color:#4D7A97; float:left; } .overviewSummary td, .memberSummary td, .typeSummary td, .useSummary td, .constantsSummary td, .deprecatedSummary td { text-align:left; padding:0px 0px 12px 10px; } th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th, td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{ vertical-align:top; padding-right:0px; padding-top:8px; padding-bottom:3px; } th.colFirst, th.colLast, th.colOne, .constantsSummary th { background:#dee3e9; text-align:left; padding:8px 3px 3px 7px; } td.colFirst, th.colFirst { white-space:nowrap; font-size:13px; } td.colLast, th.colLast { font-size:13px; } td.colOne, th.colOne { font-size:13px; } .overviewSummary td.colFirst, .overviewSummary th.colFirst, .useSummary td.colFirst, .useSummary th.colFirst, .overviewSummary td.colOne, .overviewSummary th.colOne, .memberSummary td.colFirst, .memberSummary th.colFirst, .memberSummary td.colOne, .memberSummary th.colOne, .typeSummary td.colFirst{ width:25%; vertical-align:top; } td.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 { font-weight:bold; } .tableSubHeadingColor { background-color:#EEEEFF; } .altColor { background-color:#FFFFFF; } .rowColor { background-color:#EEEEEF; } /* Content styles */ .description pre { margin-top:0; } .deprecatedContent { margin:0; padding:10px 0; } .docSummary { padding:0; } ul.blockList ul.blockList ul.blockList li.blockList h3 { font-style:normal; } div.block { font-size:14px; font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; } td.colLast div { padding-top:0px; } td.colLast a { padding-bottom:3px; } /* Formatting effect styles */ .sourceLineNo { color:green; padding:0 30px 0 0; } h1.hidden { visibility:hidden; overflow:hidden; font-size:10px; } .block { display:block; margin:3px 10px 2px 0px; color:#474747; } .deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink, .overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel, .seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink { font-weight:bold; } .deprecationComment, .emphasizedPhrase, .interfaceName { font-style:italic; } div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, div.block div.block span.interfaceName { font-style:normal; } div.contentContainer ul.blockList li.blockList h2{ padding-bottom:0px; } ================================================ FILE: src/xyscope/XYWavetable.java ================================================ package xyscope; /* * Special update to Minim Waveform class by Hansi Raber (Sep 2018), * fixing an array out of bounds bug when changing waveform quickly. * */ import java.util.Random; import ddf.minim.ugens.Waveform; /** * Wavetable wraps a float array of any size and lets you sample the array using * a normalized value [0,1]. This means that if you have an array that is 2048 * samples long, then value(0.5) will give you the 1024th sample. You will most * often use Wavetables as the Waveform in an Oscil, but other uses are also * possible. Additionally, Wavetable provides a set of methods for transforming * the samples it contains. * * @example Synthesis/WavetableMethods * * @related Waveform * @related Waves * @related WavetableGenerator * * @author Mark Godfrey <mark.godfrey@gatech.edu> */ public class XYWavetable implements Waveform { private float[] waveform; /** * Construct a Wavetable that contains size entries. * * @param size * int: the number of samples the Wavetable should contain * * @related Wavetable */ public XYWavetable(int size) { waveform = new float[size]; } /** * Construct a Wavetable that will use waveform as the float * array to sample from. This will not copy waveform, * it will use it directly. * * @param waveform * float[]: the float array this Wavetable will sample * * @related Wavetable */ public XYWavetable(float[] waveform) { this.waveform = waveform; } /** * Make a new Wavetable that has the same waveform values as * wavetable. This will copy the values from the * provided Wavetable into this Wavetable's waveform. * * @param wavetable * Wavetable: the Wavetable to copy * * @related Wavetable */ public XYWavetable(XYWavetable wavetable) { waveform = new float[wavetable.waveform.length]; System.arraycopy( wavetable.waveform, 0, waveform, 0, waveform.length ); } /** * Sets this Wavetable's waveform to the one provided. This * will not copy the values from the provided waveform, it will use * the waveform directly. * * @param waveform * float[]: the new sample data * * @related Wavetable */ public void setWaveform(float[] waveform) { this.waveform = waveform; } /** * Returns the value of the ith entry in this Wavetable's * waveform. This is equivalent to getWaveform()[i]. * * @shortdesc Returns the value of the ith entry in this Wavetable's * waveform. * * @param i * int: the index of the sample to return * * @return float: the value of the sample at i * * @related Wavetable */ public float get(int i) { return waveform[i]; } /** * Sample the Wavetable using a value in the range [0,1]. For instance, if * the Wavetable has 1024 values in its float array, then calling value(0.5) * will return the 512th value in the array. If the result is that it needs * say the 456.65th value, this will interpolate between the surrounding * values. * * @shortdesc Sample the Wavetable using a value in the range [0,1]. * * @example Synthesis/WavetableMethods * * @param at * float: a value in the range [0, 1] * * @return float: this Wavetable sampled at the requested interval * * @related Wavetable */ public float value(float at) { float[] wave = this.waveform; // create local waveform for thread safe if(wave.length==0) return 0; float whichSample = wave.length * (((at%1)+1)%1); // linearly interpolate between the two samples we want. int lowSamp = ((int)whichSample)%wave.length; int hiSamp = (lowSamp + 1)%wave.length; float rem = whichSample - lowSamp; return wave[lowSamp] + rem * ( wave[hiSamp] - wave[lowSamp] ); } /** * Returns the underlying waveform, not a copy of it. * * @return float[]: the float array managed by this Wavetable * * @related Wavetable */ public float[] getWaveform() { return waveform; } /** * Sets the ith entry of the underlying waveform to * value. This is equivalent to: *

* getWaveform()[i] = value; * * @param i * int: the index of the sample to set * @param value * float: the new sample value * * @related Wavetable */ public void set(int i, float value) { waveform[i] = value; } /** * Returns the length of the underlying waveform. This is equivalent to: *

* getWaveform().length * * @return int: the length of the underlying float array * * @related Wavetable */ public int size() { return waveform.length; } /** * Multiplies each value of the underlying waveform by scale. * * @param scale * float: the amount to scale the Wavetable with * * @related Wavetable */ public void scale(float scale) { for ( int i = 0; i < waveform.length; i++ ) { waveform[i] *= scale; } } /** * Apply a DC offset to this Wavetable. In other words, add * amount to every sample. * * @param amount * float: the amount to add to every sample in the table * * @related Wavetable */ public void offset(float amount) { for ( int i = 0; i < waveform.length; ++i ) { waveform[i] += amount; } } /** * Normalizes the Wavetable by finding the largest amplitude in the table * and scaling the table by the inverse of that amount. The result is that * the largest value in the table will now have an amplitude of 1 and * everything else is scaled proportionally. * * @example Synthesis/WavetableMethods * * @related Wavetable */ public void normalize() { float max = Float.MIN_VALUE; for ( int i = 0; i < waveform.length; i++ ) { if ( Math.abs( waveform[i] ) > max ) max = Math.abs( waveform[i] ); } scale( 1 / max ); } /** * Flips the table around 0. Equivalent to flip(0). * * @see #flip(float) * @related flip ( ) * @related Wavetable */ public void invert() { flip( 0 ); } /** * Flip the values in the table around a particular value. For example, if * you flip around 2, values greater than 2 will become less than two by the * same amount and values less than 2 will become greater than 2 by the same * amount. 3 -> 1, 0 -> 4, etc. * * @shortdesc Flip the values in the table around a particular value. * * @example Synthesis/WavetableMethods * * @param in * float: the value to flip the table around * * @related Wavetable */ public void flip(float in) { for ( int i = 0; i < waveform.length; i++ ) { if ( waveform[i] > in ) waveform[i] = in - ( waveform[i] - in ); else waveform[i] = in + ( in - waveform[i] ); } } /** * Adds Gaussian noise to the waveform. * * @example Synthesis/WavetableMethods * * @param sigma * float: the amount to scale the random values by, in effect how * "loud" the added noise will be. * * @related Wavetable */ public void addNoise(float sigma) { Random rgen = new Random(); for ( int i = 0; i < waveform.length; i++ ) { waveform[i] += ( (float)rgen.nextGaussian() ) * sigma; } } /** * Inverts all values in the table that are less than zero. -1 -> 1, -0.2 -> 0.2, etc. * * @example Synthesis/WavetableMethods * * @related Wavetable */ public void rectify() { for ( int i = 0; i < waveform.length; i++ ) { if ( waveform[i] < 0 ) waveform[i] *= -1; } } /** * Smooth out the values in the table by using a moving average window. * * @example Synthesis/WavetableMethods * * @param windowLength * int: how many samples large the window should be * * @related Wavetable */ public void smooth(int windowLength) { if ( windowLength < 1 ) return; float[] temp = (float[])waveform.clone(); for ( int i = windowLength; i < waveform.length; i++ ) { float avg = 0; for ( int j = i - windowLength; j <= i; j++ ) { avg += temp[j] / windowLength; } waveform[i] = avg; } } /** * Warping works by choosing a point in the waveform, the warpPoint, and * then specifying where it should move to, the warpTarget. Both values * should be normalized (i.e. in the range [0,1]). What will happen is that * the waveform data in front of and behind the warpPoint will be squashed * or stretch to fill the space defined by where the warpTarget is. For * instance, if you took Waves.SQUARE and called warp( 0.5, 0.2 ), you would * wind up with a square wave with a 20 percent duty cycle, the same as * using Waves.square( 0.2 ). This is because the crossover point of a * square wave is halfway through and warping it such that the crossover is * moved to 20% through the waveform is equivalent to changing the duty * cycle. Or course, much more interesting things happen when warping a more * complex waveform, such as one returned by the Waves.randomNHarms method, * especially if it is warped more than once. * * @shortdesc Warping works by choosing a point in the waveform, the * warpPoint, and then specifying where it should move to, the * warpTarget. * * @example Synthesis/WavetableMethods * * @param warpPoint * float: the point in the wave for to be moved, expressed as a * normalized value. * @param warpTarget * float: the point in the wave to move the warpPoint to, * expressed as a normalized value. * * @related Wavetable */ public void warp(float warpPoint, float warpTarget) { float[] newWave = new float[waveform.length]; for ( int s = 0; s < newWave.length; ++s ) { float lookup = (float)s / newWave.length; if ( lookup <= warpTarget ) { // normalize look up to [0,warpTarget], expand to [0,warpPoint] lookup = ( lookup / warpTarget ) * warpPoint; } else { // map (warpTarget,1] to (warpPoint,1] lookup = warpPoint + ( 1 - ( 1 - lookup ) / ( 1 - warpTarget ) ) * ( 1 - warpPoint ); } newWave[s] = value( lookup ); } waveform = newWave; } } ================================================ FILE: src/xyscope/XYscope.java ================================================ /* * cc ted davis 2017-23 */ package xyscope; import processing.core.*; import static processing.core.PApplet.*; import java.util.ArrayList; // minim import ddf.minim.*; import ddf.minim.ugens.*; import javax.sound.sampled.*; /** * Render vector graphics on a vector display (oscilloscope * X-Y mode, laser) by converting them to audio . * */ public class XYscope { // myParent is a reference to the parent sketch PApplet myParent; /** * Collection of current shapes rendered by buildWaves(). */ public XYShapeList shapes = new XYShapeList(); // minim public Minim minim, minimZ; public AudioRecorder recorder; /** * minim AudioOutput, for customizing audio out. */ public AudioOutput outXY, outZ; /** * minim Summer, for customizing patching filters. */ public Summer sumXY, sumZ; /** * minim Oscil, for customizing XYZ oscillators. */ public Oscil waveX, waveY, waveZ; /** * minim Wavetable, for customizing XYZ oscillators. */ public XYWavetable tableX, tableY, tableZ; Pan panX = new Pan(-1); Pan panY = new Pan(1); Mixer.Info[] mixerInfo; float initAmp = 1.0f; PVector amp = new PVector(initAmp, initAmp, initAmp); float initFreq = 50f; // 43.065 PVector freq = new PVector(initFreq, initFreq, initFreq); // Mixing audio channels AudioOutput mixXY; boolean useMix = false; int sampleRate = 44100; // def 44100 int bufferSize = 512; // def 1024 String mixerName = ""; int waveSizeVal = bufferSize; int waveSizeValOG = waveSizeVal; int maxPoints = waveSizeValOG; public float[] shapeX = new float[waveSizeVal]; public float[] shapeY = new float[waveSizeVal]; public float[] shapeZ = new float[waveSizeVal]; float[] shapePreX = new float[waveSizeVal]; float[] shapePreY = new float[waveSizeVal]; float[] shapePreZ = new float[waveSizeVal]; boolean debugWave = false; int debugSize = 10; boolean busy = false; boolean useLimitPoints = false; int limitPointsVal = waveSizeValOG; int ellipseDetail = 30; boolean useEase = false; float easeVal = .1f; boolean useZ = false; boolean useSmooth = false; int smoothVal = 12; boolean useLimitPath = false; float limitVal = 1; float zaxisMax = 1f; float zaxisMin = -1f; int zoffset = 1; // TYPE VARS String hershey_font[]; int hheight = 21; float hleading = 30f; float hfactor = 1; int textAlignX = 37; int textAlignY = 101; /** * List of built-in Hershey Fonts available */ public 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"}; // VECTREX VARS boolean useVectrex = false; float vectrexAmp = .82f; float vectrexAmpInit = .6f; int vectrexRotation = 0; int vectrexWidth = 310; int vectrexHeight = 410; // LASER VARS boolean useLaser = false; public Minim minimR, minimBG; /** * minim Oscil, for customizing Laser RGB oscillators. */ public Oscil waveR, waveG, waveB; public XYWavetable tableR, tableG, tableB; Pan panR = new Pan(1); Pan panG = new Pan(-1); Pan panB = new Pan(1); public AudioOutput outR, outGB; public float[] shapeR = new float[waveSizeVal]; public float[] shapeG = new float[waveSizeVal]; public float[] shapeB = new float[waveSizeVal]; private XYShape RGBshape = new XYShape(); PVector lsFreq = new PVector(initFreq, initFreq, initFreq); PVector lsWB = new PVector(250, 220, 90); PVector lsMin = new PVector(0, 0, 0); PVector lsDash = new PVector(1, 1, 1); MoogFilter moog; float laserLPFVal = 10000.0f; float laserCutoffVal = 20f; int xyWidth, xyHeight; /** * Initialize library in setup(), use default system audio out setting. * * @param theParent * PApplet to apply to, typically 'this' */ public XYscope(PApplet theParent) { myParent = theParent; welcome(); initMinim(); setOutput(); } /** * Initialize instance of XYscope and patch to an already existing signal. * * @param theParent * PApplet to apply to, typically 'this' * @param outMix * AudioOutput to merge instance and of XYscope to */ public XYscope(PApplet theParent, AudioOutput outMix) { myParent = theParent; welcome(); initMinim(); setWaveTable(outMix); useMix = true; } /** * Initialize library in setup(), custom soundcard by String for XY. * * @param theParent * PApplet to apply to, typically 'this' * @param xyMixer * Name of custom sound mixer to use for XY. */ public XYscope(PApplet theParent, String xyMixer) { myParent = theParent; welcome(); getMixerInfo(); initMinim(); setMixer(xyMixer); setOutput(); } /** * Initialize library in setup(), using default soundcard and set custom * sample rate (44100, 192000). * * @param theParent * PApplet to apply to, typically 'this' * @param sampleR * Sample rate for soundcard (44100, 48000, 96000, 192000). */ public XYscope(PApplet theParent, int sampleR) { myParent = theParent; welcome(); initMinim(); sampleRate = sampleR; setOutput(); } /** * Initialize library in setup(), custom soundcard by string for XY and set * custom sample rate (44100, 192000). * * @param theParent * PApplet to apply to, typically 'this' * @param xyMixer * Name of custom sound mixer to use for XY. * @param sampleR * Sample rate for soundcard (44100, 48000, 96000, 192000). */ public XYscope(PApplet theParent, String xyMixer, int sampleR) { myParent = theParent; welcome(); getMixerInfo(); initMinim(); setMixer(xyMixer); sampleRate = sampleR; setOutput(); } /** * Initialize library in setup(), custom soundcard by string for XY and set * custom sample rate (44100, 192000). * * @param theParent * PApplet to apply to, typically 'this' * @param xyMixer * Name of custom sound mixer to use for XY. * @param sampleRateVal * Sample rate for soundcard (44100, 48000, 96000, 192000). * @param bufferSizeVal * Size of buffer/latency for cpu/soundcard (128, 256, 512, 1024, 2048). */ public XYscope(PApplet theParent, String xyMixer, int sampleRateVal, int bufferSizeVal) { myParent = theParent; welcome(); getMixerInfo(); initMinim(); setMixer(xyMixer); sampleRate = sampleRateVal; bufferSize = bufferSizeVal; setOutput(); } private void welcome() { System.out.println("XYscope 3.0.0 - https://teddavis.org/xyscope"); xyWidth = myParent.width; xyHeight = myParent.height; initText(); } private void initText() { textFont("meteorology"); } /** * Lists all audio input/output options available */ public void getMixerInfo() { mixerInfo = AudioSystem.getMixerInfo(); for (int i = 0; i < mixerInfo.length; i++) { println(i + " = " + mixerInfo[i].getName()); } } private static Mixer getMixerByName(String toFind) { for (Mixer.Info info : AudioSystem.getMixerInfo()) { if (toFind.equals(info.getName())) { return AudioSystem.getMixer(info); } } return null; } private void initMinim() { minim = new Minim(myParent); minimZ = new Minim(myParent); minimR = new Minim(myParent); minimBG = new Minim(myParent); } // *** add mixer() as getter and setter?? add bufferSize as initial option param private void setMixer(String xyMixer) { getMixerInfo(); Mixer mixer = getMixerByName(xyMixer); minim.setOutputMixer(mixer); } private void setOutput() { outXY = minim.getLineOut(Minim.STEREO, bufferSize, sampleRate); setWaveTable(); } public int sampleRate() { return sampleRate; } public void sampleRate(int sampleRateVal) { sampleRate = sampleRateVal; setOutput(); } public int bufferSize() { return outXY.bufferSize(); } public void bufferSize(int bufferSizeVal) { if(bufferSizeVal > 16) { bufferSize = bufferSizeVal; } setOutput(); } private void setWaveTable() { sumXY = new Summer(); sumXY.setChannelCount(2); tableX = new XYWavetable(2); waveX = new Oscil(freq.x, amp.x, tableX); tableX.setWaveform(shapeX); waveX.patch(panX).patch(sumXY); tableY = new XYWavetable(2); waveY = new Oscil(freq.y, amp.y, tableY); tableY.setWaveform(shapeY); waveY.patch(panY).patch(sumXY); waveReset(); sumXY.patch(outXY); } private void setWaveTable(AudioOutput outMix) { tableX = new XYWavetable(2); waveX = new Oscil(freq.x, amp.x, tableX); tableX.setWaveform(shapeX); waveX.patch(panX).patch(outMix); tableY = new XYWavetable(2); waveY = new Oscil(freq.y, amp.y, tableY); tableY.setWaveform(shapeY); waveY.patch(panY).patch(outMix); mixXY = outMix; waveReset(); } private void setWaveTableZ() { if (useZ) { tableZ = new XYWavetable(2); waveZ = new Oscil(freq.z, amp.z, tableZ); tableZ.setWaveform(shapeZ); waveZ.patch(outZ); // need pan?? or gets full amp to both channels? waveReset(); sumXY.unpatch(outXY); sumXY.patch(outXY); } } /** * Reset time-step used by XYZ oscillators if they slip when changing * frequencies. * */ public void waveReset() { waveX.reset(); waveY.reset(); if (useZ) waveZ.reset(); } /** * Reset time-step used by XYZ oscillators if they slip when changing * frequencies. * */ public void resetWaves() { waveReset(); } /** * Patch z-axis to custom soundcard by String. Note: Auto z-axis has been * disabled until solved, until then, one can manually buildZ() * * @param zMixer * Name of custom sound mixer to use for Z. */ public void z(String zMixer) { Mixer mixerZ = getMixerByName(zMixer); minimZ.setOutputMixer(mixerZ); outZ = minimZ.getLineOut(Minim.STEREO, waveSizeValOG); useZ = true; setWaveTableZ(); } /** * Patch z-axis to custom soundcard by String and set custom sample rate * (44100, 192000). Note: Auto z-axis has been disabled until solved, until * then, one can manually buildZ() * * @param zMixer * Name of custom sound mixer to use for Z. * * @param sampleR * Sample rate for soundcard (44100, 48000, 192000). * */ public void z(String zMixer, int sampleR) { Mixer mixerZ = getMixerByName(zMixer); minimZ.setOutputMixer(mixerZ); outZ = minimZ.getLineOut(Minim.STEREO, waveSizeValOG, sampleR); useZ = true; setWaveTableZ(); } /** * Check if z-axis waveform is being automatically drawn from added shapes. * * @return boolean */ public boolean zAuto() { return useZ; } /** * Enabled by default for automatic generation of z-axis waveform based on * added shapes. Disable if creating your own waveform (used for blanking, * dotted line, etc. Note: Only works if using a second audio output channel * * @param zAutoBool * true/false for generating z waveform */ public void zAuto(boolean zAutoBool) { useZ = zAutoBool; } /** * Get ArrayList of all coordinates used for vector drawing as an ArrayList * of PVector's. * * @return ArrayList of PVector */ public ArrayList wavePoints() { return shapes.getPoints(); } /** * Get min and max values for z-axis output as two value array. * * @return array containing [zaxisMin, zaxisMax] */ public float[] zRange() { float[] zRangeFloat = { zaxisMin, zaxisMax }; return zRangeFloat; } /** * Set min and max values for z-axis output. Necessary for any inverted * z-axis devices. *

* default is zMin: 1, zMax: -1 * * @param zMin * float between -1 to 1 * @param zMax * float between -1 to 1 */ public void zRange(float zMin, float zMax) { zaxisMin = zMin; zaxisMax = zMax; } /** * Returns current value for limiting drawing by number of points. * * @return limitVal */ public float limitPoints(){ return limitPointsVal; } /** * Limit drawing by number of points * * @param newLimitPointsVal int for limiting number of points */ public void limitPoints(int newLimitPointsVal){ if(newLimitPointsVal == 0){ useLimitPoints = false; }else{ limitPointsVal = abs(newLimitPointsVal); useLimitPoints = true; } } /** * Returns current value for border that limits rendering to edges of screen. * * @return limitVal */ public float limitPath(){ return limitVal; } /** * Only render points within specified border from the edge * * @param newLimitVal float for border limit from edges */ public void limitPath(float newLimitVal){ limitVal = newLimitVal; useLimitPath = true; } /** * 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. * */ public void vectrex(){ vectrex(vectrexWidth, vectrexHeight, vectrexAmpInit, vectrexRotation); } /** * 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. * * @param vrot * int for degrees of rotation, 90 or -90 */ public void vectrex(int vrot){ if(vrot == 90){ vectrexRotation = vrot; vectrex(vectrexHeight, vectrexWidth, vectrexAmpInit, vectrexRotation); }else if(vrot == -90){ vectrexRotation = vrot; vectrex(vectrexHeight, vectrexWidth, vectrexAmpInit, vectrexRotation); }else { vectrexRotation = 0; vectrex(vectrexWidth, vectrexHeight, vectrexAmpInit, vectrexRotation); } } /** * Use XYscope on a modded Vectrex monitor for XYZ input. Set custom width, height, initial amplitude scaling and rotation/orientation. * * @param vw * int for width of canvas, default is 330 * @param vh * int for height of canvas, default is 410 * @param vamp * float for initial amplitude adjustment of signal to screen (0.0 - 1.0), default is .6 * @param vrot * int for degrees of rotation, 90 or -90, default is 0 */ public void vectrex(int vw, int vh, float vamp, int vrot){ useVectrex = true; vectrexRotation = vrot; myParent.getSurface().setResizable(true); myParent.getSurface().setSize(vw, vh); xyWidth = vw; xyHeight = vh; vectrexAmpInit = vamp; amp(vectrexAmpInit); } /** * Get current amplitude difference used for ratio of Vectrex. * * @return float */ public float vectrexRatio(){ return vectrexAmp; } /** * Set current amplitude difference used for ratio of Vectrex. * * @param vectrexAmpVal * float for amplitude difference (0.0 - 1.0), default is .82 */ public void vectrexRatio(float vectrexAmpVal){ vectrexAmp = constrain(vectrexAmpVal, 0f, 1f); amp(vectrexAmpInit); } /** * Activate use of Laser's RGB by assigning 3 additional (2x stereo pairs) audio channels for controlling the RGB modulation. * * @param inR * String for name of audio channel for Red * @param inBG * String for name of audio channel for Blue/Green */ public void laser(String inR, String inBG){ Mixer mixerR = getMixerByName(inR); Mixer mixerBG = getMixerByName(inBG); minimR.setOutputMixer(mixerR); minimBG.setOutputMixer(mixerBG); outR = minimR.getLineOut(Minim.STEREO, waveSizeValOG); outGB = minimBG.getLineOut(Minim.STEREO, waveSizeValOG); setWaveTableRGB(); useLaser = true; //LPF moog = new MoogFilter( laserLPFVal, 0f ); moog.setChannelCount(2); sumXY.unpatch(outXY); sumXY.patch(moog).patch(outXY); } private void setWaveTableRGB() { tableR = new XYWavetable(2); waveR = new Oscil(freq.x, amp.x, tableR); tableR.setWaveform(shapeR); waveR.patch(panR).patch(outR); tableG = new XYWavetable(2); waveG = new Oscil(freq.x, amp.x, tableG); tableG.setWaveform(shapeG); waveG.patch(panG).patch(outGB); tableB = new XYWavetable(2); waveB = new Oscil(freq.x, amp.x, tableB); tableB.setWaveform(shapeB); waveB.patch(panB).patch(outGB); } /** * Returns current frequency of Laser's low-pass-filter (LPF). * * @return float */ public float laserLPF(){ return laserLPFVal; } /** * Set new frequency for Laser's low-pass-filter (LPF) as float. Be careful to stay within range that's safe for your Galvos. * * @param newLaserLPFVal * float between 0.1 - 20000.0 */ public void laserLPF(float newLaserLPFVal){ laserLPFVal = constrain(newLaserLPFVal, .1f, 20000f); moog.frequency.setLastValue(laserLPFVal); } /** * Returns current value spot-killer (minimum size of drawing for laser). * * @return float */ public float spotKiller(){ return laserCutoffVal; } /** * Set new value for spotKiller (won't draw if XY shape is smaller than provided value). * * @param newLaserCutoffVal * float */ public void spotKiller(float newLaserCutoffVal){ laserCutoffVal = abs(newLaserCutoffVal); } /** * Returns current minimum values set for RGB laser. * * @return PVector */ public PVector strokeMin(){ return lsMin; } /** * Set new minimum values for RGB laser, as 3 floats. * * @param minR * value between 0.0 - 255.0 * @param minG * value between 0.0 - 255.0 * @param minB * value between 0.0 - 255.0 */ public void strokeMin(float minR, float minG, float minB){ strokeMin(new PVector(minR, minG, minB)); } /** * Set new minimum values for RGB laser, as PVector. * * @param minPV * PVector with 3 values between 0.0 - 255.0 */ public void strokeMin(PVector minPV){ lsMin = new PVector(minPV.x, minPV.y, minPV.z); } /** * Returns current white balance (mixture for white) settings for RGB laser. * * @return PVector */ public PVector strokeWB(){ return lsWB; } /** * Set white balance (mixture for white) for RGB laser, as 3 floats. * * @param wbR * value between 0.0 - 255.0 * @param wbG * value between 0.0 - 255.0 * @param wbB * value between 0.0 - 255.0 */ public void strokeWB(float wbR, float wbG, float wbB){ strokeWB(new PVector(wbR, wbG, wbB)); } /** * Set white balance (mixture for white) for RGB laser, as PVector. * * @param wbPV * PVector with 3 values between 0.0 - 255.0 */ public void strokeWB(PVector wbPV){ lsWB = new PVector(wbPV.x, wbPV.y, wbPV.z); } /** * Returns current dashes used for RGB waves of laser. * * @return PVector */ public PVector strokeDash(){ return lsDash; } /** * Set same number of dashes for RGB laser. * * @param newDash * int */ public void strokeDash(int newDash){ strokeDash(new PVector(newDash, newDash, newDash)); } /** * Set seperate number of dashes per color for RGB laser. * * @param newDashR * int * @param newDashG * int * @param newDashB * int */ public void strokeDash(int newDashR, int newDashG, int newDashB){ strokeDash(new PVector(newDashR, newDashG, newDashB)); } /** * Set dashes for RGB laser, as PVector. * * @param newDash * PVector with 3 values */ public void strokeDash(PVector newDash){ lsDash = new PVector(newDash.x, newDash.y, newDash.z); } /** * Set stroke for RGB laser, as 3 floats. * * @param r * float from 0.0 – 255.0 * @param g * float from 0.0 – 255.0 * @param b * float from 0.0 – 255.0 */ public void stroke(float r, float g, float b){ stroke(new PVector(r, g, b)); } /** * Set stroke for RGB laser, as PVector. * * @param newDash * PVector with 3 values, from 0.0 – 255.0 */ public void stroke(PVector rgb){ float mr = 0f; float mg = 0f; float mb = 0f; if(rgb.x > 0f) mr = map(rgb.x, 0f, 255f, (lsMin.x/255f), 1f); if(rgb.y > 0f) mg = map(rgb.y, 0f, 255f, (lsMin.y/255f), 1f); if(rgb.z > 0f) mb = map(rgb.z, 0f, 255f, (lsMin.z/255f), 1f); if(rgb.x == 255f && rgb.y == 255f && rgb.z == 255f){ mr = lsWB.x / 255f; mg = lsWB.y / 255f; mb = lsWB.z / 255f; } RGBshape.add(new PVector(mr, mg, mb)); } /** * Get current frequency for R, G, B oscillators for laser as a PVector. * * @return PVector */ public PVector strokeFreq() { return lsFreq; } /** * Set new frequency for all RGB oscillators of laser together as single float. * * @param newFreq * float */ public void strokeFreq(float newFreq) { lsFreq = new PVector(newFreq, newFreq, newFreq); strokeFreq(lsFreq); } /** * Set new frequency for all R + G + B oscillators of laser. * * @param newFreqR * float * @param newFreqG * float * @param newFreqB * float */ public void strokeFreq(float newFreqR, float newFreqG, float newFreqB) { lsFreq = new PVector(newFreqR, newFreqG, newFreqB); strokeFreq(lsFreq); } /** * Set new frequency for each RGB oscillator of laser separately using a PVector. * * @param newFreq * PVector */ public void strokeFreq(PVector newFreq) { lsFreq = newFreq; waveR.setFrequency(lsFreq.x); waveG.setFrequency(lsFreq.y); waveB.setFrequency(lsFreq.z); } /** * Get current amplitude setting of XY oscillators. * * @return float */ public PVector amp() { return amp; } /** * Set new amplitude for both XYZ oscillators as float. * * @param newAmp * value between 0.0 - 1.0 */ public void amp(float newAmp) { amp.x = constrain(newAmp, 0f, 1f); amp.y = constrain(newAmp, 0f, 1f); if(useVectrex){ amp.x *= vectrexAmp; } waveX.setAmplitude(amp.x); waveY.setAmplitude(amp.y); if (useZ) { amp.z = constrain(newAmp, 0f, 1f); waveZ.setAmplitude(amp.z); } } /** * Set new amplitude for both X + Y oscillators as float. * * @param newAmpX * value between 0.0 - 1.0 * @param newAmpY * value between 0.0 - 1.0 */ public void amp(float newAmpX, float newAmpY) { amp.x = constrain(newAmpX, 0f, 1f); if(useVectrex) amp.x *= vectrexAmp; amp.y = constrain(newAmpY, 0f, 1f); waveX.setAmplitude(amp.x); waveY.setAmplitude(amp.y); } /** * Set new amplitude for both X + Y + Z oscillators as float. * * @param newAmpX * value between 0.0 - 1.0 * @param newAmpY * value between 0.0 - 1.0 * @param newAmpZ * value between 0.0 - 1.0 */ public void amp(float newAmpX, float newAmpY, float newAmpZ) { amp.x = constrain(newAmpX, 0f, 1f); if(useVectrex) amp.x *= vectrexAmp; amp.y = constrain(newAmpY, 0f, 1f); waveX.setAmplitude(amp.x); waveY.setAmplitude(amp.y); if (useZ) { amp.z = constrain(newAmpZ, 0f, 1f); waveZ.setAmplitude(amp.z); } } /** * Set new amplitude for each XYZ oscillator separately using a PVector for * the values. * * @param newAmp * PVector of values between 0.0 - 1.0 */ public void amp(PVector newAmp) { float tempX = constrain(newAmp.x, 0f, 1f); if(useVectrex) tempX *= vectrexAmp; float tempY = constrain(newAmp.y, 0f, 1f); waveX.setAmplitude(tempX); waveY.setAmplitude(tempY); if (useZ) { float tempZ = constrain(newAmp.z, 0f, 1f); waveZ.setAmplitude(tempZ); } } /** * Get current frequency for X, Y, Z oscillators as a PVector. * * @return PVector */ public PVector freq() { return freq; } /** * Set new frequency for all XYZ oscillators together as single float. * * @param newFreq * float */ public void freq(float newFreq) { freq = new PVector(newFreq, newFreq, newFreq); waveX.setFrequency(freq.x); waveY.setFrequency(freq.y); if (useZ) waveZ.setFrequency(freq.z); } /** * Set new frequency for all X + Y oscillators. * * @param newFreqX * float * @param newFreqY * float */ public void freq(float newFreqX, float newFreqY) { freq.x = newFreqX; freq.y = newFreqY; waveX.setFrequency(freq.x); waveY.setFrequency(freq.y); } /** * Set new frequency for all X + Y + Z oscillators. * * @param newFreqX * float * @param newFreqY * float * @param newFreqZ * float */ public void freq(float newFreqX, float newFreqY, float newFreqZ) { freq.x = newFreqX; freq.y = newFreqY; waveX.setFrequency(freq.x); waveY.setFrequency(freq.y); if (useZ) { freq.z = newFreqZ; waveZ.setFrequency(freq.z); } } /** * Set new frequency for each XYZ oscillator separately using a PVector for * the values. * * @param newFreq * PVector */ public void freq(PVector newFreq) { freq = newFreq; waveX.setFrequency(freq.x); waveY.setFrequency(freq.y); if (useZ) waveZ.setFrequency(freq.z); } /** * Adjust the (x, y) panning, mainly useful if swapping cables digitally. Default (-1.0, 1.0) * * @param panXVal * float - pan for x/left channel * @param panYVal * float - pan for y/right channel */ public void pan(float panXVal, float panYVal) { panX.setPan(panXVal); panY.setPan(panYVal); } /** * Enable/Disable easing transitions from one set of buildWaves() to the * next. Default is false. Deprecated in v2.0+ * * @param easeBool * true/false */ public void ease(boolean easeBool) { useEase = easeBool; } /** * Check if easing between each frame of buildWaves() is enabled. * Deprecated in v2.0+ * * @return boolean */ public boolean ease() { return useEase; } /** * Returns current easeAmount, 0.0 - 1.0. Deprecated in v2.0+ * * @return float */ public float easeAmount() { return easeVal; } /** * Set new easing value for speed between buildWave() transitions. Deprecated in v2.0+ * * @param newEaseValue * float between 0.0 - 1.0 */ public void easeAmount(float newEaseValue) { easeVal = newEaseValue; } /** * Enable/Disable debug view for comparing waveform to shape. * * @param debugBool * true/false */ public void debugView(boolean debugBool) { debugWave = debugBool; } /** * Check if debugView is active. * * @return boolean */ public boolean debugView() { return debugWave; } /** * Get size of wavetables. By default, it's the same as the * outXY.bufferSize() * * @return newSize * int */ public int waveSize() { return waveSizeVal; } /** * Set custom size for wavetables. By default, it's the same as the * outXY.bufferSize() * * @param newSize * int */ public void waveSize(int newSize) { waveSizeVal = newSize; shapeY = new float[waveSizeVal]; shapeX = new float[waveSizeVal]; // shapeZ = new float[waveSizeVal]; shapePreY = new float[waveSizeVal]; shapePreX = new float[waveSizeVal]; // shapePreZ = new float[waveSizeVal]; tableX.setWaveform(shapeX); tableY.setWaveform(shapeY); // if (useZ) // tableZ.setWaveform(shapeZ); } /** * Clears the waveforms from previous buildWaves(). Useful to call at top of * draw(), similar to using background() to clear the slate before building * the waveforms at the bottom of your draw with buildWaves(). */ public void clearWaves() { if(!busy){ for (int i = 0; i < shapeX.length; i++) { shapePreX[i] = 0; shapePreY[i] = 0; } if (useZ) { for (int i = 0; i < shapeZ.length; i++) { shapePreZ[i] = zaxisMin; } } shapes = new XYShapeList(); currentShape = null; if(useLaser) RGBshape = new XYShape(); } } private double lerp(double start, double end, double amt) { return start + (end-start)*amt; } public void buildWavesTest(ArrayList wc) { if(wc.size() > 128) { waveSize(floor((wc.size())/2)*2); } // waveSize(1024); float[] mfx = new float[wc.size()]; float[] mfy = new float[wc.size()]; for(int i=0; i 0) { double wave_size = shapes.getPoints().size()*stepsSize; double total_dist = shapes.getDistance(); double tot = 0.0; ArrayList wave_col = new ArrayList(); for (int i=0; i < shapes.size(); i++) { double shapeDist = shapes.get(i).getDistance(); ArrayList pv = shapes.get(i); for (int j=0; j < pv.size()-1; j++) { // what about point? PVector p1 = pv.get(j); PVector p2 = pv.get(j+1); double line_dist = dist(p1.x, p1.y, p2.x, p2.y); tot += line_dist; double sec_per = Math.round(1+line_dist / total_dist * wave_size); double steps = 1.0 / sec_per; for (int k=0; k <= sec_per; k++) { PVector seg = PVector.lerp(p1, p2, (float)((float)k*steps)); wave_col.add(seg); } } } float[] mfx = new float[waveSize()]; float[] mfy = new float[waveSize()]; float[] mfz = new float[waveSize()]; for(int i=0; i 0) { XYWavetable mx = new XYWavetable(2); XYWavetable my = new XYWavetable(2); XYWavetable mz = new XYWavetable(2); float[] mfx = new float[0]; float[] mfy = new float[0]; float[] mfz = new float[0]; for (XYShape shape : shapes) { XYWavetable tx = new XYWavetable(2); XYWavetable ty = new XYWavetable(2); XYWavetable tz = new XYWavetable(2); float[] tfx = new float[shape.size()]; float[] tfy = new float[shape.size()]; float[] tfz = new float[shape.size()]; for (int i = 0; i < shape.size(); i++) { if(i < tfx.length){ PVector tc = shape.get(i); tfx[i] = map(tc.x, 0f, 1f, -1f, 1f); tfy[i] = map(tc.y, 0f, 1f, 1f, -1f); tfz[i] = zaxisMin; if(tc.z == 1f) tfz[i] = zaxisMax; float tfxx = tfx[i]; float tfyy = tfy[i]; if(useVectrex){ if(vectrexRotation == 90){ tfx[i] = tfyy; tfy[i] = tfxx*-1; }else if(vectrexRotation == -90){ tfx[i] = tfyy*-1; tfy[i] = tfxx; }else if(vectrexRotation == 0){ tfx[i] = tfxx*-1; tfy[i] = tfyy*-1; } } } } tx.setWaveform(tfx); ty.setWaveform(tfy); tz.setWaveform(tfz); mfx = concat(mfx, tx.getWaveform()); mfy = concat(mfy, ty.getWaveform()); mfz = concat(mfz, tz.getWaveform()); } setWaveforms(mfx, mfy, mfz); if(useLaser){ buildLaser(); } }else{ emptyWave(); } } else if (bwm == -2) { // waveform gen v2 may 2018 if (shapes.size() > 0) { if (shapes.totalSize() < waveSizeValOG) { if (waveSize() != shapes.totalSize()) { waveSize(shapes.totalSize()); } int SID = 0; if (waveSize() == shapes.totalSize()) { for (XYShape shape : shapes) { for (int i = 0; i < shape.size(); i++) { shapePreX[SID] = map(shape.get(i).x, 0f, 1f, -1f, 1f); shapePreY[SID] = map(shape.get(i).y, 0f, 1f, 1f, -1f); SID++; } } } } else { if (waveSize() != waveSizeValOG) waveSize(waveSizeValOG); ArrayList ts = shapes.getPoints(); for (int i = 0; i < shapeX.length; i++) { int ptsSel = (int) Math.floor(map(i, 0f, shapeX.length, 0, ts.size())); shapePreX[i] = map(ts.get(ptsSel).x, 0f, 1f, -1f, 1f); shapePreY[i] = map(ts.get(ptsSel).y, 0f, 1f, 1f, -1f); } } } else { waveSize(1); shapePreX[0] = 0f; shapePreY[0] = 0f; } } else if (bwm == -1) { // waveform gen v1 jul 2017 if (waveSize() != waveSizeValOG) waveSize(waveSizeValOG); if (shapes.size() > 0) { ArrayList ts = shapes.getPoints(); for (int i = 0; i < shapeX.length; i++) { int ptsSel = (int) Math.floor(map(i, 0f, shapeX.length, 0, ts.size())); shapePreX[i] = map(ts.get(ptsSel).x, 0f, 1f, -1f, 1f); shapePreY[i] = map(ts.get(ptsSel).y, 0f, 1f, 1f, -1f); } } } if(bwm == -1 || bwm == -2){ // easing if (useEase) { easeWaves(); } else { for (int i = 0; i < shapePreX.length; i++) { shapeX[i] = shapePreX[i]; shapeY[i] = shapePreY[i]; if(useVectrex){ if(vectrexRotation == 90){ shapeX[i] = shapePreY[i]; shapeY[i] = shapePreX[i]*-1; }else if(vectrexRotation == -90){ shapeX[i] = shapePreY[i]*-1; shapeY[i] = shapePreX[i]; }else if(vectrexRotation == 0){ shapeX[i] = shapePreX[i]*-1; shapeY[i] = shapePreY[i]*-1; } } if (useZ) shapeZ[i] = shapePreZ[i]; } if (useZ) { for (int i = 0; i < shapePreZ.length; i++) { shapeZ[i] = shapePreZ[i]; } } } // smooth if (useSmooth) { tableX.smooth(smoothVal); tableY.smooth(smoothVal); } } } public void setWaveforms(float[] mfx, float[] mfy) { float[] mfz = new float[0]; setWaveforms(mfx, mfy, mfz); } public void setWaveforms(float[] mfx, float[] mfy, float[] mfz) { // limit points if(useLimitPoints && (mfx.length > limitPointsVal || mfy.length > limitPointsVal)){ float[] lx = new float[limitPointsVal]; float[] ly = new float[limitPointsVal]; float[] lz = new float[limitPointsVal]; for(int i=0; i < limitPointsVal; i++){ int mfxSel = floor(map(i, 0, limitPointsVal, 0, mfx.length)); int mfySel = floor(map(i, 0, limitPointsVal, 0, mfy.length)); int mfzSel = floor(map(i, 0, limitPointsVal, 0, mfz.length)); lx[i] = mfx[mfxSel]; ly[i] = mfy[mfySel]; lz[i] = mfz[mfzSel]; } tableX.setWaveform(lx); tableY.setWaveform(ly); if(useZ) tableZ.setWaveform(lz); }else{ tableX.setWaveform(mfx); tableY.setWaveform(mfy); if(useZ) tableZ.setWaveform(mfz); } } private void emptyWave() { tableX.setWaveform(new float[0]); tableY.setWaveform(new float[0]); if(useZ) tableZ.setWaveform(new float[0]); if(useLaser){ tableR.setWaveform(new float[0]); tableG.setWaveform(new float[0]); tableB.setWaveform(new float[0]); } } // *** remove?? public void warpWave(int mx, int pmx) { float warpPoint = constrain( (float)pmx / myParent.width, 0, 1 ); float warpTarget = constrain( (float)mx / myParent.width, 0, 1 ); tableX.warp( warpPoint, warpTarget ); tableY.warp( warpPoint, warpTarget ); } private void buildLaser() { // spotkiller checkSize boolean checkSize = false; AudioOutput tempXY = outXY; for (int i = 0; i < tempXY.bufferSize() - 1; i++) { float lAudio = abs(tempXY.left.get(i) * (float) xyWidth / 2); float rAudio = abs(tempXY.right.get(i) * (float) xyHeight / 2); if(lAudio > laserCutoffVal || rAudio > laserCutoffVal) checkSize = true; } if(checkSize){ if(RGBshape.size() > 0){ buildColorWave(tableR, "x", floor(lsDash.x)); buildColorWave(tableG, "y", floor(lsDash.y)); buildColorWave(tableB, "z", floor(lsDash.z)); }else{ float[] tfr = {lsWB.x/255f}; float[] tfg = {lsWB.y/255f}; float[] tfb = {lsWB.z/255f}; tableR.setWaveform(tfr); tableG.setWaveform(tfg); tableB.setWaveform(tfb); } }else{ tableR.setWaveform(new float[0]); tableG.setWaveform(new float[0]); tableB.setWaveform(new float[0]); } } private void buildColorWave(XYWavetable tableTemp, String RGBval, int dashTemp){ XYShape shapeTemp = new XYShape(); for (int i = 0; i < RGBshape.size()*dashTemp; i++) { int RGBshapeIndex = floor(map(i, 0, RGBshape.size()*dashTemp, 0, RGBshape.size())); PVector tc = RGBshape.get(RGBshapeIndex); if(i%2==0 && dashTemp > 1) { tc = new PVector(-1, -1, -1); } shapeTemp.add(tc); } float[] tfr = new float[shapeTemp.size()]; for (int i = 0; i < shapeTemp.size(); i++) { PVector tc = shapeTemp.get(i); if(RGBval == "x") { tfr[i] = tc.x; }else if(RGBval == "y") { tfr[i] = tc.y; }else if(RGBval == "z") { tfr[i] = tc.z; } } tableTemp.setWaveform(tfr); } /** * Generate the XY(Z) oscillator waveforms from all added points/shapes for * sending audio to vector display. Call this after drawing any primitive * shapes. */ public void buildWaves() { buildWaves(0); } /** * Build custom X oscillator waveform. Use waveSize() to ensure you send the * right number of values. * * @param newWave * Array of normalized floats between 0.0 - 1.0 * * @see #waveSize() * */ public void buildX(float[] newWave) { for (int i = 0; i < newWave.length; i++) { int sel = (int) Math.floor(map(i, 0f, newWave.length, 0f, shapePreX.length)); shapePreX[i] = map(newWave[sel], 0f, 1f, -1f, 1f); } if (useEase) { easeWaves(shapePreX, shapeX); } else { for (int i = 0; i < shapePreX.length; i++) { shapeX[i] = shapePreX[i]; } } } /** * Build custom Y oscillator waveform. Use waveSize() to ensure you send the * right number of values. * * @param newWave * Array of normalized floats between 0.0 - 1.0 * @see #waveSize() */ public void buildY(float[] newWave) { for (int i = 0; i < newWave.length; i++) { int sel = (int) Math.floor(map(i, 0f, newWave.length, 0f, shapePreY.length)); shapePreY[i] = map(newWave[sel], 0f, 1f, 1f, -1f); } if (useEase) { easeWaves(shapePreY, shapeY); } else { for (int i = 0; i < shapePreY.length; i++) { shapeY[i] = shapePreY[i]; } } } /** * Build custom Z oscillator waveform. Use waveSize() to ensure you send the * right number of values. * * @param newWave * Array of normalized floats between 0.0 - 1.0 * @see #waveSize() */ public void buildZ(float[] newWave) { for (int i = 0; i < newWave.length; i++) { int sel = (int) Math.floor(map(i, 0f, newWave.length, 0f, shapePreZ.length)); shapePreZ[i] = map(newWave[sel], 0f, 1f, zaxisMin, zaxisMax); } if (useEase) { easeWaves(shapePreZ, shapeZ); } else { for (int i = 0; i < shapePreZ.length; i++) { shapeZ[i] = shapePreZ[i]; } } } private void easeWaves(float[] sShape, float[] tShape) { for (int i = 0; i < sShape.length; i++) { float targetX = sShape[i]; float dx = targetX - tShape[i]; tShape[i] += dx * easeVal; } } private void easeWaves() { easeWaves(shapePreX, shapeX); easeWaves(shapePreY, shapeY); if (useZ) { easeWaves(shapePreZ, shapeZ); } } /** * Check if waveform smoothing is enabled/disabled. Deprecated since v3 * * @return boolean */ public boolean smoothWaves() { return useSmooth; } /** * Enable/disable Smooth waveforms to reduce visibility of points in * drawing. Default is false. Deprecated since v3 * * @param smoothWavesBool * true/false */ public void smoothWaves(boolean smoothWavesBool) { useSmooth = smoothWavesBool; } /** * Get number of steps for smoothing waveforms. Deprecated since v3 * * @return int * * @see Minim * » Wavetable » smooth() */ public int smoothWavesAmount() { return smoothVal; } /** * Set number of steps for smoothing waveforms. Default is 12. Deprecated since v3 * * @param swAmount * new int value for smoothening waveform * @see Minim * » Wavetable » smooth() */ public void smoothWavesAmount(int swAmount) { smoothVal = swAmount; } /** * Draw all information *

    *
  • drawPath() *
  • drawWaveform() *
  • drawWave() *
  • drawXY() *
  • drawPoints() *
*/ public void drawAll() { drawPath(); drawWaveform(); drawWave(); drawXY(); drawPoints(); } public void drawPath() { drawPath(255, 255, 255); } /** * Draw path of points remapped from normalized values to width + height of * sketch. */ public void drawPath(float r, float g, float b) { myParent.pushStyle(); myParent.noFill(); myParent.stroke(r, g, b); myParent.pushMatrix(); myParent.beginShape(); for (XYShape shape : shapes) { for (int i = 0; i < shape.size(); i++) { float x = map(shape.get(i).x, 0f, 1f, 0f, xyWidth); float y = map(shape.get(i).y, 0f, 1f, 0f, xyHeight); myParent.vertex(x, y); } } myParent.endShape(OPEN); myParent.popMatrix(); myParent.popStyle(); } public void drawPoints() { drawPoints(0, 255, 0); } /** * Draw points (as 3px ellipses) remapped from normalized values to width + * height of sketch. */ public void drawPoints(float r, float g, float b) { myParent.pushStyle(); myParent.fill(r, g, b); myParent.noStroke(); myParent.pushMatrix(); for (XYShape shape : shapes) { for (int i = 0; i < shape.size(); i++) { float x = map(shape.get(i).x, 0f, 1f, 0f, xyWidth); float y = map(shape.get(i).y, 0f, 1f, 0f, xyHeight); myParent.ellipse(x, y, 3, 3); } } myParent.popMatrix(); myParent.noFill(); myParent.popStyle(); } public void drawXY() { drawXY(50, 255, 50); } /** * Simulate X-Y mode of oscilloscope output. */ public void drawXY(float r, float g, float b) { myParent.pushStyle(); myParent.noFill(); myParent.stroke(r, g, b); myParent.pushMatrix(); myParent.translate(xyWidth / 2, xyHeight / 2); myParent.beginShape(); AudioOutput tempXY; if (useMix) { tempXY = mixXY; } else { tempXY = outXY; } for (int i = 0; i < tempXY.bufferSize() - 1; i++) { float lAudio = tempXY.left.get(i) * (float) xyWidth / 2; float rAudio = tempXY.right.get(i) * (float) xyHeight / 2; if(useVectrex){ if(vectrexRotation == 90){ rAudio = tempXY.left.get(i) * (float) xyHeight / 2; lAudio = tempXY.right.get(i) * (float) xyWidth / 2 * -1f * vectrexAmp; }else if(vectrexRotation == -90){ rAudio = tempXY.left.get(i) * (float) xyHeight / 2 * -1f; lAudio = tempXY.right.get(i) * (float) xyWidth / 2 * vectrexAmp; }else if(vectrexRotation == 0){ lAudio = tempXY.left.get(i) * (float) xyWidth / 2 * -1f; rAudio = tempXY.right.get(i) * (float) xyHeight / 2 * -1f * vectrexAmp; } } myParent.curveVertex(lAudio, rAudio * -1f); } myParent.endShape(); if (debugWave) { float mouseT = (myParent.mouseX / (float) xyWidth); float mx = tableX.value(mouseT) * (float) xyWidth / 2 * amp.x; float my = -tableY.value(mouseT) * (float) xyHeight / 2 * amp.y; myParent.pushStyle(); myParent.fill(r, g, b); myParent.noStroke(); myParent.ellipse(mx, my, debugSize, debugSize); myParent.popStyle(); } myParent.popMatrix(); myParent.popStyle(); } public void drawWaveform(){ drawWaveform(50, 50, 255, 255, 50, 50); } /** * Draw waveform of all XYZ oscillators. *
    *
  • Red: X
  • *
  • Blue: Y
  • *
  • Green: Z
  • *
*/ public void drawWaveform(float lr, float lg, float lb, float rr, float rg, float rb) { myParent.pushStyle(); myParent.noFill(); myParent.pushMatrix(); myParent.beginShape(); // X -> L myParent.stroke(lr, lg, lb); for (int i = 0; i < xyWidth; i++) { myParent.vertex(i, (float) xyHeight * .25f - ((float) xyHeight * .125f) * tableX.value((float) i / (float) xyWidth)); } myParent.endShape(); // Y -> R myParent.stroke(rr, rg, rb); myParent.beginShape(); for (int i = 0; i < xyWidth; i++) { myParent.vertex(i, (float) xyHeight * .75f - ((float) xyHeight * 0.125f) * tableY.value((float) i / (float) xyWidth)); } myParent.endShape(); if (debugWave) { float mouseT = (myParent.mouseX / (float) xyWidth); float lx = myParent.mouseX; float ly = (float) xyHeight * .25f - ((float) xyHeight * .125f) * tableX.value((float) myParent.mouseX / (float) xyWidth); float ry = (float) xyHeight * .75f - ((float) xyHeight * .125f) * tableY.value((float) myParent.mouseX / (float) xyWidth); myParent.pushStyle(); myParent.noStroke(); myParent.fill(lr, lg, lb); myParent.ellipse(lx - 2, ly, debugSize, debugSize); myParent.fill(rr, rg, rb); myParent.ellipse(lx - 2, ry, debugSize, debugSize); myParent.popStyle(); } // Z if (useZ) { myParent.stroke(50, 255, 50); myParent.beginShape(); for (int i = 0; i < xyWidth; i++) { myParent.vertex(i, (float) xyHeight * .5f - ((float) xyHeight * 0.125f) * tableZ.value((float) i / (float) xyWidth)); } myParent.endShape(); } myParent.popMatrix(); myParent.popStyle(); } /** * Draw waveform of all laser RGB oscillators. *
    *
  • Red: R
  • *
  • Green: G
  • *
  • Blue: B
  • *
*/ public void drawRGB(){ if(useLaser){ myParent.pushStyle(); myParent.noFill(); myParent.pushMatrix(); //R myParent.stroke(255, 50, 50); myParent.beginShape(); for (int i = 0; i < xyWidth; i++) { myParent.vertex(i, (float) xyHeight * .25f - ((float) xyHeight * 0.125f) * tableR.value((float) i / (float) xyWidth)); } myParent.endShape(); //G myParent.stroke(50, 255, 50); myParent.beginShape(); for (int i = 0; i < xyWidth; i++) { myParent.vertex(i, (float) xyHeight * .5f - ((float) xyHeight * 0.125f) * tableG.value((float) i / (float) xyWidth)); } myParent.endShape(); //B myParent.stroke(50, 50, 255); myParent.beginShape(); for (int i = 0; i < xyWidth; i++) { myParent.vertex(i, (float) xyHeight * .75f - ((float) xyHeight * 0.125f) * tableB.value((float) i / (float) xyWidth)); } myParent.endShape(); myParent.popMatrix(); myParent.popStyle(); } } /** * Draw wave of all XYZ oscillators. */ public void drawWave() { myParent.pushStyle(); myParent.stroke(255); myParent.noFill(); myParent.pushMatrix(); myParent.beginShape(); AudioOutput tempXY; if (useMix) { tempXY = mixXY; } else { tempXY = outXY; } for (int i = 0; i < tempXY.bufferSize() - 1; i++) { float xAudio = map(i, 0, tempXY.bufferSize(), 0, xyWidth); float lAudio = tempXY.left.get(i); // curveVertex(lAudio, rAudio*-1); myParent.vertex(xAudio, xyHeight * .25f - (xyHeight * .25f) * lAudio); } myParent.endShape(); myParent.beginShape(); for (int i = 0; i < tempXY.bufferSize() - 1; i++) { float xAudio = map(i, 0, tempXY.bufferSize(), 0, xyWidth); float rAudio = tempXY.right.get(i); // curveVertex(lAudio, rAudio*-1); myParent.vertex(xAudio, xyHeight * .75f + (xyHeight * .25f) * rAudio); } myParent.endShape(); if (useZ) { myParent.beginShape(); for (int i = 0; i < outZ.bufferSize() - 1; i++) { float xAudio = map(i, 0, outZ.bufferSize(), 0, xyWidth); float lAudio = outZ.left.get(i); // curveVertex(lAudio, rAudio*-1); myParent.vertex(xAudio, xyHeight * .5f - (xyHeight * .25f) * lAudio); } myParent.endShape(); } myParent.popMatrix(); myParent.popStyle(); } int rectM; /** * Get detail (number of points) for drawing an ellipse. * * @return int */ public int ellipseDetail() { return ellipseDetail; } /** * Set detail (number of points) for drawing an ellipse. * * @param newED * int for facets of ellipse */ public void ellipseDetail(int newED) { ellipseDetail = abs(newED); } /** * Set detail (number of points) for drawing an ellipse. * * @param newED * float for facets of ellipse (rounded) */ public void ellipseDetail(float newED) { ellipseDetail = abs(round(newED)); } /** * Set rectMode (similar to Processing function). * * @param rectModeVal * CORNER or CENTER * @see Processing * Reference » rectMode() */ public void rectMode(int rectModeVal) { if (rectModeVal == 0) { rectM = 0; } else if (rectModeVal == 3) { rectM = 3; } } int stepsSize = 24; /** * Get steps multiplier (number of segments) between each point. * * @return int */ public int steps() { return stepsSize; } /** * Set steps multiplier (number of segments) between each point. * * @param newSteps * float for segments multiplier (rounded) */ public void steps(float newSteps) { stepsSize = parseInt(newSteps); } // non-params drawing public void point() { line(xyWidth/2, xyHeight/2, xyWidth/2+1, xyHeight/2+1); } /** * Draw point, expects point(x, y). * * @param x float - x position of point * @param y float - y position of point * * @see Processing * Reference » point() */ public void point(float x, float y) { line(x, y, x+1, y+1); } /** * Draw point, expects point(x, y, z). * * @param x float - x position of point * @param y float - y position of point * @param z float - z position of point * * @see Processing * Reference » point() */ public void point(float x, float y, float z) { line(x, y, z, x+1, y+1, z+1); } // non-params drawing public void line() { beginShape(); vertex(0, 0); vertex(xyWidth, xyHeight); endShape(); } /** * Draw line, expects line(x1, y1, x2, y2). * * @param x1 float - first x position of point * @param y1 float - first y position of point * @param x2 float - second x position of point * @param y2 float - second y position of point * * @see Processing * Reference » line() */ public void line(float x1, float y1, float x2, float y2) { beginShape(); vertex(x1, y1); vertex(x2, y2); endShape(); } /** * Draw line, expects line(x1, y1, z1, x2, y2, z2). * * @param x1 float - first x position of point * @param y1 float - first y position of point * @param z1 float - first z position of point * @param x2 float - second x position of point * @param y2 float - second y position of point * @param z2 float - second z position of point * * @see Processing * Reference » line() */ public void line(float x1, float y1, float z1, float x2, float y2, float z2) { beginShape(); vertex(x1, y1, z1); vertex(x2, y2, z2); endShape(); } // non-params drawing public void square() { int x = xyWidth/2; int y = xyHeight/2; int w = xyHeight; if (rectM == 3) { x -= w / 2; y -= w / 2; } vertexRect(x, y, w, w); } /** * Draw square, expects square(x, y, extent). * * @param x float - x position of square * @param y float - y position of square * @param extent float - width + height of square * * @see Processing * Reference » rect() */ public void square(float x, float y, float e) { if (rectM == 3) { x -= e / 2; y -= e / 2; } vertexRect(x, y, e, e); } // non-params drawing public void rect() { int x = xyWidth/2; int y = xyHeight/2; int w = xyHeight; if (rectM == 3) { x -= w / 2; y -= w / 2; } vertexRect(x, y, w, w); } /** * Draw rectangle (square), expects rect(x, y, w). * * @param x float - x position of rectangle * @param y float - y position of rectangle * @param w float - width of rectangle * * @see Processing * Reference » rect() */ public void rect(float x, float y, float w) { if (rectM == 3) { x -= w / 2; y -= w / 2; } vertexRect(x, y, w, w); } /** * Draw rectangle, expects rect(x, y, w, h). * * @param x float - x position of rectangle * @param y float - y position of rectangle * @param w float - width of rectangle * @param h float - height of rectangle * * @see Processing * Reference » rect() */ public void rect(float x, float y, float w, float h) { if (rectM == 3) { x -= w / 2; y -= h / 2; } vertexRect(x, y, w, h); } private void vertexRect(float x1, float y1, float w1, float h1) { beginShape(); vertex(x1, y1); vertex(x1 + w1, y1); vertex(x1 + w1, y1 + h1); vertex(x1, y1 + h1); vertex(x1, y1); // if (useZ) // vertex(x1, y1); endShape(); } // non-params drawing public void circle() { vertexEllipse(xyWidth/2, xyHeight/2, xyHeight, xyHeight); } public void ellipse() { vertexEllipse(xyWidth/2, xyHeight/2, xyHeight, xyHeight); } /** * Draw circle (ellipse), expects circle(x, y, extent). * * @param x float - x position of circle * @param y float - y position of circle * @param extent float - width + height of circle * * @see Processing * Reference » ellipse() */ public void circle(float x, float y, float e) { vertexEllipse(x, y, e, e); } /** * Draw ellipse (circle), expects ellipse(x, y, w). * * @param x float - x position of ellipse * @param y float - y position of ellipse * @param d float - diameter of ellipse * * @see Processing * Reference » ellipse() */ public void ellipse(float x, float y, float d) { vertexEllipse(x, y, d, d); } /** * Draw ellipse, expects ellipse(x, y, w, h). * * @param x float - x position of ellipse * @param y float - y position of ellipse * @param w float - width of ellipse * @param h float - height of ellipse * * @see Processing * Reference » ellipse() */ public void ellipse(float x, float y, float w, float h) { vertexEllipse(x, y, w, h); } // vertexEllipse! // based on // http://stackoverflow.com/questions/5886628/effecient-way-to-draw-ellipse-with-opengl-or-d3d private void vertexEllipse(float cx, float cy, float rx, float ry) { float theta = TWO_PI / (float) ellipseDetail; float c = cos(theta);// precalculate the sine and cosine float s = sin(theta); float t; float x = .5f;// we start at angle = 0 float y = 0f; beginShape(); for (int ii = 0; ii < ellipseDetail + 1; ii++) { // apply radius and offset vertex(x * rx + cx, y * ry + cy);// output vertex // apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } endShape(); } // non-param drawings public void lissajous(){ lissajous(xyWidth/2, xyHeight/2, xyHeight/2, 1, 2, 0, 180); } /** * Draw lissajous curve, expects lissajous(xPos, yPos, radius, ratioA, ratioB, phase, resolution). * * @param xPos float - x position of lissajous * @param yPos float - y position of lissajous * @param radius float - size of lissajous * @param ratioA float - lissajous ratio part A * @param ratioB float - lissajous ratio part B * @param phase float - phase of lissajous curve * @param resolution float - number of points for lissajous curve * */ public void lissajous(float xPos, float yPos, float radius, float ratioA, float ratioB, float phase, float resolution){ resolution = constrain(resolution, 1f, 360f); beginShape(); for(int i=0; i < resolution+1; i++){ float theta = TWO_PI/resolution; float x = sin(i*theta*ratioA)*radius; float y = sin(radians(phase)+i*theta*ratioB)*radius; vertex(xPos + x, yPos + y); } endShape(); } public void box() { box(xyHeight/2, xyHeight/2, xyHeight/2); } /** * Draw box, expects box(size). * * @param size float - size of box * * @see Processing * Reference » box() */ public void box(float size) { box(size, size, size); } /** * Draw box, expects box(rx, ryz). * * @param rx float - size of box in x-axis * @param ryz float - size of box in y/z-axis * * @see Processing * Reference » box() */ public void box(float rx, float ryz) { box(rx, ryz, ryz); } /** * Draw box, expects box(rx, ry, rz). * * @param rx float - size of box in x-axis * @param ry float - size of box in y-axis * @param rz float - size of box in z-axis * * @see Processing * Reference » box() */ // extended from: https://stackoverflow.com/a/72277489/10885535 public void box(float rxt, float ryt, float rzt) { // half size: keep the pivot at the center of the mesh float rx = rxt * .5f; float ry = ryt * .5f; float rz = rzt * .5f; beginShape(); // back (-z) vertex(-rx, -ry, -rz); vertex(+rx, -ry, -rz); vertex(+rx, +ry, -rz); vertex(-rx, +ry, -rz); // slide to otherside vertex(-rx, -ry, -rz); // front (+z) vertex(-rx, -ry, +rz); vertex(+rx, -ry, +rz); vertex(+rx, +ry, +rz); vertex(-rx, +ry, +rz); // top (-y) vertex(-rx, -ry, +rz); vertex(-rx, -ry, -rz); vertex(+rx, -ry, -rz); vertex(+rx, -ry, +rz); // bottom (+y) vertex(+rx, +ry, +rz); vertex(+rx, +ry, -rz); vertex(-rx, +ry, -rz); vertex(-rx, +ry, +rz); // left (-x) vertex(-rx, -ry, +rz); vertex(-rx, -ry, -rz); vertex(-rx, +ry, -rz); vertex(-rx, +ry, +rz); // slide to otherside vertex(-rx, -ry, +rz); // right (+x) vertex(+rx, -ry, +rz); vertex(+rx, -ry, -rz); vertex(+rx, +ry, -rz); vertex(+rx, +ry, +rz); endShape(); } // based on: Examples » Topics » Textures » Texture Sphere int dx = 24; int dy = 24; // non-param drawing public void sphere() { ellipsoid(xyHeight/3, xyHeight/3, xyHeight/3, dx, dy); } public void ellipsoid() { ellipsoid(xyHeight/3, xyHeight/3, xyHeight/3, dx, dy); } /** * Draw sphere, expects sphere(size). * * @param size float - size of sphere * * @see Processing * Reference » sphere() */ public void sphere(float rs) { ellipsoid(rs, rs, rs, dx, dy); } /** * Draw sphere, expects sphere(size, verticesCount). * * @param size float - size of sphere * @param verticesCount int - number of horzontal + vertical vertices * * @see Processing * Reference » sphere() */ public void sphere(float rs, int dxy) { ellipsoid(rs, rs, rs, dxy, dxy); } /** * Draw sphere, expects sphere(size, verticiesW, verticiesH). * * @param size float - size of sphere * @param verticiesW int - number of horzontal vertices * @param verticiesH int - number of vertical vertices * * @see Processing * Reference » sphere() */ public void sphere(float rs, int dx, int dy) { ellipsoid(rs, rs, rs, dx, dy); } /** * Draw ellipsoid, expects ellipsoid(rx, ry, rz). * * @param rx float - size in x-axis * @param ry float - size in y-axis * @param rz float - size in z-axis * * @see Processing * Reference » sphere() */ public void ellipsoid(float rx, float ry, float rz) { ellipsoid(rx, ry, rz, dx, dy); } /** * Draw ellipsoid, expects ellipsoid(rx, ry, rz, verticesCount). * * @param rx float - size in x-axis * @param ry float - size in y-axis * @param rz float - size in z-axis * @param verticesCount int - number of horzontal + vertical vertices * * @see Processing * Reference » sphere() */ public void ellipsoid(float rx, float ry, float rz, int dxy) { ellipsoid(rx, ry, rz, dxy, dxy); } /** * Draw ellipsoid, expects ellipsoid(rx, ry, rz, verticesCount). * * @param rx float - size in x-axis * @param ry float - size in y-axis * @param rz float - size in z-axis * @param verticiesW int - number of horzontal vertices * @param verticiesH int - number of vertical vertices * * @see Processing * Reference » sphere() */ public void ellipsoid(float rxt, float ryt, float rzt, int dx, int dy) { float rx = rxt;// * .5f; float ry = ryt;// * .5f; float rz = rzt;// * .5f; int numPointsW; int numPointsH_2pi; int numPointsH; float[] coorX; float[] coorY; float[] coorZ; float[] multXZ; int numvW = constrain(dx, 1, 50); int numvH_2pi = constrain(dy, 1, 50); // The number of points around the width and height numPointsW=numvW+1; numPointsH_2pi=numvH_2pi; // How many actual pts around the sphere (not just from top to bottom) numPointsH=ceil((float)numPointsH_2pi/2f)+1; // How many pts from top to bottom (abs(....) b/c of the possibility of an odd numPointsH_2pi) coorX=new float[numPointsW]; // All the x-coor in a horizontal circle radius 1 coorY=new float[numPointsH]; // All the y-coor in a vertical circle radius 1 coorZ=new float[numPointsW]; // All the z-coor in a horizontal circle radius 1 multXZ=new float[numPointsH]; // The radius of each horizontal circle (that you will multiply with coorX and coorZ) for (int i=0; i 0) { vertex(vertices2[j].x, vertices2[j].y, vertices2[j].z); } vertices2[j].x = cos(radians(latheAngle))*vertices[j].x; vertices2[j].y = sin(radians(latheAngle))*vertices[j].x; vertices2[j].z = vertices[j].z; vertex(vertices2[j].x, vertices2[j].y, vertices2[j].z); } latheAngle+=360.0f/dy; endShape(); } } /** * Begin multi-vertex shape. * * @see Processing * Reference » beginShape() */ public void beginShape() { currentShape = new XYShape(); shapes.add(currentShape); } public void vertex() { vertex(new PVector(myParent.random(xyWidth), myParent.random(xyHeight), 0), false); } public void curveVertex() { vertex(new PVector(myParent.random(xyWidth), myParent.random(xyHeight), 0), false); } /** * Currently sent as normal vertex (to be fixed). Simply here for code » * vectorcode compatibility. * * @param x float - x position of vertex point * @param y float - y position of vertex point * * @see Processing * Reference » curveVertex() */ public void curveVertex(float x, float y) { vertex(new PVector(x, y, 0), false); } /** * Currently sent as normal vertex (to be fixed). Simply here for code » * vectorcode compatibility. * * @param x float - x position of vertex point * @param y float - y position of vertex point * @param z float - z position of vertex point * * @see Processing * Reference » curveVertex() */ public void curveVertex(float x, float y, float z) { vertex(new PVector(x, y, z), true); } /** * Add vertex to complex shape. Expects vertex(x, y). * * @param x float - x position of vertex point * @param y float - y position of vertex point * * @see Processing * Reference » vertex() */ public void vertex(float x, float y) { vertex(new PVector(x, y, 0), false); } /** * Add vertex to complex shape. Expects vertex(x, y, z). * * @param x float - x position of vertex point * @param y float - y position of vertex point * @param z float - z position of vertex point * * @see Processing * Reference » vertex() */ public void vertex(float x, float y, float z) { vertex(new PVector(x, y, z), true); } /** * Add vertex to complex shape. Expects vertex(PVector()). * * @param p PVector – pass xy[z] position as PVector. * * @see Processing * Reference » vertex() */ public void vertex(PVector p) { if(p.z == 0f) { vertexAdd(p, false); }else { vertexAdd(p, true); } } private void vertex(PVector p, boolean mode3D) { vertexAdd(p, mode3D); } private void vertexAdd(PVector p, boolean mode3D) { float x, y; if(mode3D){ x = norm(myParent.screenX(p.x, p.y, p.z), 0f, xyWidth + 0f); y = norm(myParent.screenY(p.x, p.y, p.z), 0f, xyHeight + 0f); }else{ x = norm(myParent.screenX(p.x, p.y), 0f, xyWidth + 0f); y = norm(myParent.screenY(p.x, p.y), 0f, xyHeight + 0f); } PVector normP = new PVector(x, y, 0); if(useLimitPath){ float sx, sy; if(mode3D){ sx = myParent.screenX(p.x, p.y, p.z); sy = myParent.screenY(p.x, p.y, p.z); }else{ sx = myParent.screenX(p.x, p.y); sy = myParent.screenY(p.x, p.y); } if ((sx >= limitVal && sx <= xyWidth - limitVal) && (sy >= limitVal && sy <= xyHeight - limitVal)) { currentShape.add(normP); }else { endShape(); beginShape(); // initShape = true; } }else{ currentShape.add(normP); } } /** * End complex shape. * * @see Processing * Reference » endShape() */ public void endShape(int close) { if(close == 2) { if(currentShape.size() > 0) { PVector lastC = currentShape.get(0); currentShape.add(new PVector(lastC.x, lastC.y, 0)); } } endShape(); // // not necessary in current setup. maybe useful later for z-axis // if(currentShape.size() > 1) { // currentShape.get(currentShape.size()-1).z = 1f; // }else { // // Calculate index of last element // int index = shapes.size() - 1; // // // Delete last element by passing index // shapes.remove(index); // } } /** * End complex shape. * * @see Processing * Reference » endShape() */ public void endShape() { // not necessary in current setup. maybe useful later for z-axis if(currentShape.size() > 1) { currentShape.get(currentShape.size()-1).z = 1f; }else { // Calculate index of last element int index = shapes.size() - 1; // Delete last element by passing index shapes.remove(index); } } /* * RECORDER OUT FUNCTIONS */ public void recorderBegin() { recorderBegin("XYscope"); } public void recorderBegin(String filename) { String date = new java.text.SimpleDateFormat("yyyy_MM_dd_kkmmssSSS").format(new java.util.Date()); recorder = minim.createRecorder(outXY, filename + "_" + date + ".wav"); recorder.beginRecord(); println("XYscope - beginRecord"); } public void recorderEnd() { recorder.endRecord(); recorder.save(); println("XYscope - endRecord + saved!"); } /* * HERSHEY Font meets Processing text() functions * HUGE THX to this lib: https://github.com/ixd-hof/HersheyFont * * */ private boolean contains(String[] arr, String val) { for(int i=0; i= 48 && line.charAt(0) <= 57) hershey_font_string += line + "\n"; else { hershey_font_string = hershey_font_string.substring(0, hershey_font_string.length()-1) + line + "\n"; } } hershey_font = hershey_font_string.split("\n"); } } /** * Get size for built-in Hershey text rendering. * * @return Float - size of text */ public float textSize(){ return hfactor; } /** * Set size for built-in Hershey text rendering. * * @param fontSize * Float - size of text */ public void textSize(float fontSize){ hfactor = fontSize/hheight; } /** * Get the spacing between lines of text in units of pixels. * * @return Float - the size in pixels for spacing between lines */ public float textLeading(){ return hleading; } /** * Sets the spacing between lines of text in units of pixels. * * @param fontLeading * Float - the size in pixels for spacing between lines */ public void textLeading(float leading){ hleading = leading; } /** * Set horizontal alignment of built in Hershey text rending. LEFT (default), CENTER or RIGHT. * * @param taX * LEFT, CENTER, RIGHT */ public void textAlign(int taX) { textAlign(taX, textAlignY); } /** * Set horizontal and vertical alignment of built in Hershey text rending. Horizontal: LEFT (default), CENTER or RIGHT. Vertical: TOP, CENTER (default), BOTTOM. * * @param taX * LEFT, CENTER, RIGHT * @param taY * TOP, CENTER, BOTTOM */ public void textAlign(int taX, int taY) { if(taX == 37 || taX == 3 || taX == 39) { textAlignX = taX; } if(taY == 101 || taY == 3 || taY == 102) { textAlignY = taY; } } // non-param drawing public void text() { text("XYscope", xyWidth/2, xyHeight/2); } public void text(float s, float x, float y) { text(nf(s), x, y); } public void text(int s, float x, float y) { text(nf(s), x, y); } /** * Render text using built in Hershey Fonts. * * @param s * String - text to display * @param x * float - horizontal position of text * @param y * float - vertical position of text */ public void text(String s, float x, float y) { String[] parts = splitTokens(s, "\n\r"); switch(textAlignY) { case 101: y += hfactor * 12; break; case 102: y -= hfactor * 21 * parts.length; break; default: if(parts.length > 1) { y -= hfactor * 21 * parts.length/2; } break; } float yOffset = y; for(int i=0; i offxMax) { offxMax = offxTemp; } } return offxMax; } float textWidthParse(String s) { float offx = 0; for (int k=0; k */ public PVector[][] textPaths(String s, float x, float y) { String[] parts = splitTokens(s, "\n\r"); switch(textAlignY) { case 101: y += hfactor * 12; break; case 102: y -= hfactor * 21 * parts.length; break; default: y -= hfactor * 21 * parts.length/2; break; } ArrayList> cooords = new ArrayList(s.length()); float yOffset = y; for(int i=0; i> coords) { x += 5 * hfactor; switch(textAlignX) { case 3: x -= textWidth(s) / 2; break; case 39: x -= textWidth(s); break; } float offx = x; float offy = y; for (int k=0; k coord = new ArrayList(h_vertices[i].length()); for (int j=2; j { /** * Returns float of total distance of drawn shapes, XYShapeList. * * @return float * */ public double getDistance() { double sum = 0.0f; for (XYShape shape : this) { sum += shape.getDistance(); } return sum; } /** * Returns int of total number of points in drawn shapes, XYShapeList. * * @return float * */ public int totalSize() { int tsCounter = 0; for (XYShape shape : this) { for (int i = 0; i < shape.size(); i++) { tsCounter++; } } return tsCounter; } /** * Returns ArrayList of coordinates for all drawn shapes, XYShapeList. * * @return float * */ public ArrayList getPoints() { ArrayList gp = new ArrayList(); for (XYShape shape : this) { for (int i = 0; i < shape.size(); i++) { gp.add(shape.get(i)); } } return gp; } } public class XYShape extends ArrayList { public double getDistance() { double sum = 0.0f; for (int i = 0; i < size() - 1; i++) { sum += dist(get(i).x, get(i).y, get(i+1).x, get(i+1).y);//get(i).dist(get(i + 1)); } return sum; } } } ================================================ FILE: web/includes/css/styles.css ================================================ @font-face { font-family: 'roboto_mono'; src: url('../fonts/robotomono-regular-webfont.woff2') format('woff2'), url('../fonts/robotomono-regular-webfont.woff') format('woff'); font-weight: 400; font-style: normal; } @font-face { font-family: 'roboto_mono'; src: url('../fonts/robotomono-light-webfont.woff2') format('woff2'), url('../fonts/robotomono-light-webfont.woff') format('woff'); font-weight: 200; font-style: normal; } body { max-width: 760px; margin: 0 auto; padding: 25px; color: #333333; font-family: "roboto_mono", sans-serif; font-size: 11pt; line-height: 1.5em; font-weight:200; } a{ color:#000; } a:visited{ color:#666; } li{ line-height:2em; } h1, h2{ padding:8px 0 8px 0; } h1 { line-height: 1; font-weight:400; border-bottom: solid 2px #000; border-top: solid 2px #000; } h2 { font-size:22pt; border-top: solid 1px #000; border-bottom: solid 1px #000; margin-top:50px; } h3 { font-size:18pt; margin-top:35px; margin-bottom:0; text-decoration: underline; } h4 { font-size:14pt; margin-top:20px; margin-bottom:0; } hr{ border:none; border-bottom:1px dotted #000; margin-top:20px; } pre > code{ border-radius: 5px; border:1px solid #ddd !important; background: #fafafa !important; display: inline-block; padding: 0 8px; font-family: "roboto_mono"; font-weight: 400; } blockquote p{ background:#fafafa; border-left:3px solid #aaa; padding:5px; font-weight:400; } ================================================ FILE: web/includes/js/highlight/a11y-dark.css ================================================ /* a11y-dark theme */ /* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */ /* @author: ericwbailey */ /* Comment */ .hljs-comment, .hljs-quote { color: #d4d0ab; } /* Red */ .hljs-variable, .hljs-template-variable, .hljs-tag, .hljs-name, .hljs-selector-id, .hljs-selector-class, .hljs-regexp, .hljs-deletion { color: #ffa07a; } /* Orange */ .hljs-number, .hljs-built_in, .hljs-builtin-name, .hljs-literal, .hljs-type, .hljs-params, .hljs-meta, .hljs-link { color: #f5ab35; } /* Yellow */ .hljs-attribute { color: #ffd700; } /* Green */ .hljs-string, .hljs-symbol, .hljs-bullet, .hljs-addition { color: #abe338; } /* Blue */ .hljs-title, .hljs-section { color: #00e0e0; } /* Purple */ .hljs-keyword, .hljs-selector-tag { color: #dcc6e0; } .hljs { display: block; overflow-x: auto; background: #2b2b2b; color: #f8f8f2; padding: 0.5em; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } @media screen and (-ms-high-contrast: active) { .hljs-addition, .hljs-attribute, .hljs-built_in, .hljs-builtin-name, .hljs-bullet, .hljs-comment, .hljs-link, .hljs-literal, .hljs-meta, .hljs-number, .hljs-params, .hljs-string, .hljs-symbol, .hljs-type, .hljs-quote { color: highlight; } .hljs-keyword, .hljs-selector-tag { font-weight: bold; } } ================================================ FILE: web/includes/js/highlight/a11y-light.css ================================================ /* a11y-light theme */ /* Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css */ /* @author: ericwbailey */ /* Comment */ .hljs-comment, .hljs-quote { color: #696969; } /* Red */ .hljs-variable, .hljs-template-variable, .hljs-tag, .hljs-name, .hljs-selector-id, .hljs-selector-class, .hljs-regexp, .hljs-deletion { color: #d91e18; } /* Orange */ .hljs-number, .hljs-built_in, .hljs-builtin-name, .hljs-literal, .hljs-type, .hljs-params, .hljs-meta, .hljs-link { color: #aa5d00; } /* Yellow */ .hljs-attribute { color: #aa5d00; } /* Green */ .hljs-string, .hljs-symbol, .hljs-bullet, .hljs-addition { color: #008000; } /* Blue */ .hljs-title, .hljs-section { color: #007faa; } /* Purple */ .hljs-keyword, .hljs-selector-tag { color: #7928a1; } .hljs { display: block; overflow-x: auto; background: #fefefe; color: #545454; padding: 0.5em; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } @media screen and (-ms-high-contrast: active) { .hljs-addition, .hljs-attribute, .hljs-built_in, .hljs-builtin-name, .hljs-bullet, .hljs-comment, .hljs-link, .hljs-literal, .hljs-meta, .hljs-number, .hljs-params, .hljs-string, .hljs-symbol, .hljs-type, .hljs-quote { color: highlight; } .hljs-keyword, .hljs-selector-tag { font-weight: bold; } } ================================================ FILE: web/includes/js/highlight/docco.css ================================================ /* Docco style used in http://jashkenas.github.com/docco/ converted by Simon Madine (@thingsinjars) */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #000; background: #f8f8ff; } .hljs-comment, .hljs-quote { color: #408080; font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-subst { color: #954121; } .hljs-number { color: #40a070; } .hljs-string, .hljs-doctag { color: #219161; } .hljs-selector-id, .hljs-selector-class, .hljs-section, .hljs-type { color: #19469d; } .hljs-params { color: #00f; } .hljs-title { color: #458; font-weight: bold; } .hljs-tag, .hljs-name, .hljs-attribute { color: #000080; font-weight: normal; } .hljs-variable, .hljs-template-variable { color: #008080; } .hljs-regexp, .hljs-link { color: #b68; } .hljs-symbol, .hljs-bullet { color: #990073; } .hljs-built_in, .hljs-builtin-name { color: #0086b3; } .hljs-meta { color: #999; font-weight: bold; } .hljs-deletion { background: #fdd; } .hljs-addition { background: #dfd; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } ================================================ FILE: web/includes/js/highlight/github.css ================================================ /* github.com style (c) Vasily Polovnyov */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hljs-comment, .hljs-quote { color: #998; font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-subst { color: #333; font-weight: bold; } .hljs-number, .hljs-literal, .hljs-variable, .hljs-template-variable, .hljs-tag .hljs-attr { color: #008080; } .hljs-string, .hljs-doctag { color: #d14; } .hljs-title, .hljs-section, .hljs-selector-id { color: #900; font-weight: bold; } .hljs-subst { font-weight: normal; } .hljs-type, .hljs-class .hljs-title { color: #458; font-weight: bold; } .hljs-tag, .hljs-name, .hljs-attribute { color: #000080; font-weight: normal; } .hljs-regexp, .hljs-link { color: #009926; } .hljs-symbol, .hljs-bullet { color: #990073; } .hljs-built_in, .hljs-builtin-name { color: #0086b3; } .hljs-meta { color: #999; font-weight: bold; } .hljs-deletion { background: #fdd; } .hljs-addition { background: #dfd; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } ================================================ FILE: web/includes/js/highlight/highlight.pack.js ================================================ /* Highlight.js 10.0.1 (33af2ea5) License: BSD-3-Clause Copyright (c) 2006-2020, Ivan Sagalaev */ !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,"&").replace(//g,">")}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"}function u(e){s+=""}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="",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+=``}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||"")+'"')).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;i0&&(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?"
":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(//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:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",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:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",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}}}()); ================================================ FILE: web/includes/js/render.js ================================================ (function () { var file = file || "README.md?"+Math.floor(Math.random(9999)); var reader = new stmd.DocParser(); var writer = new stmd.HtmlRenderer(); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if(xhr.readyState === 4 && xhr.status === 200) { display(xhr); // sharable anchors if(location.hash.length > 1){ const yourElement = document.getElementById(location.hash.substring(1 )); const y = yourElement.getBoundingClientRect().top + window.pageYOffset; window.scrollTo({top: y, behavior: 'smooth'}); } // code highlighting! document.querySelectorAll('pre code').forEach((block) => { hljs.highlightBlock(block); }); } }; function display(xhr) { var parsed = reader.parse(xhr.responseText); var content = writer.renderBlock(parsed); document.getElementById('md').innerHTML = content; // fix anchors var hs = document.querySelectorAll("h1, h2, h3, h4, h5, h6"); for(var i=0; i?@[\\\\\\]^_`{|}~-]'; var ESCAPED_CHAR = '\\\\' + ESCAPABLE; var IN_DOUBLE_QUOTES = '"(' + ESCAPED_CHAR + '|[^"\\x00])*"'; var IN_SINGLE_QUOTES = '\'(' + ESCAPED_CHAR + '|[^\'\\x00])*\''; var IN_PARENS = '\\((' + ESCAPED_CHAR + '|[^)\\x00])*\\)'; var REG_CHAR = '[^\\\\()\\x00-\\x20]'; var IN_PARENS_NOSP = '\\((' + REG_CHAR + '|' + ESCAPED_CHAR + ')*\\)'; var TAGNAME = '[A-Za-z][A-Za-z0-9]*'; var 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)'; var ATTRIBUTENAME = '[a-zA-Z_:][a-zA-Z0-9:._-]*'; var UNQUOTEDVALUE = "[^\"'=<>`\\x00-\\x20]+"; var SINGLEQUOTEDVALUE = "'[^']*'"; var DOUBLEQUOTEDVALUE = '"[^"]*"'; var ATTRIBUTEVALUE = "(?:" + UNQUOTEDVALUE + "|" + SINGLEQUOTEDVALUE + "|" + DOUBLEQUOTEDVALUE + ")"; var ATTRIBUTEVALUESPEC = "(?:" + "\\s*=" + "\\s*" + ATTRIBUTEVALUE + ")"; var ATTRIBUTE = "(?:" + "\\s+" + ATTRIBUTENAME + ATTRIBUTEVALUESPEC + "?)"; var OPENTAG = "<" + TAGNAME + ATTRIBUTE + "*" + "\\s*/?>"; var CLOSETAG = "]"; var OPENBLOCKTAG = "<" + BLOCKTAGNAME + ATTRIBUTE + "*" + "\\s*/?>"; var CLOSEBLOCKTAG = "]"; var HTMLCOMMENT = ""; var PROCESSINGINSTRUCTION = "[<][?].*?[?][>]"; var DECLARATION = "]*>"; var CDATA = "])*\\]\\]>"; var HTMLTAG = "(?:" + OPENTAG + "|" + CLOSETAG + "|" + HTMLCOMMENT + "|" + PROCESSINGINSTRUCTION + "|" + DECLARATION + "|" + CDATA + ")"; var HTMLBLOCKOPEN = "<(?:" + BLOCKTAGNAME + "[\\s/>]" + "|" + "/" + BLOCKTAGNAME + "[\\s>]" + "|" + "[?!])"; var reHtmlTag = new RegExp('^' + HTMLTAG, 'i'); var reHtmlBlockOpen = new RegExp('^' + HTMLBLOCKOPEN, 'i'); var reLinkTitle = new RegExp( '^(?:"(' + ESCAPED_CHAR + '|[^"\\x00])*"' + '|' + '\'(' + ESCAPED_CHAR + '|[^\'\\x00])*\'' + '|' + '\\((' + ESCAPED_CHAR + '|[^)\\x00])*\\))'); var reLinkDestinationBraces = new RegExp( '^(?:[<](?:[^<>\\n\\\\\\x00]' + '|' + ESCAPED_CHAR + '|' + '\\\\)*[>])'); var reLinkDestination = new RegExp( '^(?:' + REG_CHAR + '+|' + ESCAPED_CHAR + '|' + IN_PARENS_NOSP + ')*'); var reEscapable = new RegExp(ESCAPABLE); var reAllEscapedChar = new RegExp('\\\\(' + ESCAPABLE + ')', 'g'); var reEscapedChar = new RegExp('^\\\\(' + ESCAPABLE + ')'); var reAllTab = /\t/g; var reHrule = /^(?:(?:\* *){3,}|(?:_ *){3,}|(?:- *){3,}) *$/; // Matches a character with a special meaning in markdown, // or a string of non-special characters. var reMain = /^(?:[\n`\[\]\\!<&*_]|[^\n`\[\]\\!<&*_]+)/m; // UTILITY FUNCTIONS // Replace backslash escapes with literal characters. var unescape = function(s) { return s.replace(reAllEscapedChar, '$1'); }; // Returns true if string contains only space characters. var isBlank = function(s) { return /^\s*$/.test(s); }; // Normalize reference label: collapse internal whitespace // to single space, remove leading/trailing whitespace, case fold. var normalizeReference = function(s) { return s.trim() .replace(/\s+/,' ') .toUpperCase(); }; // Attempt to match a regex in string s at offset offset. // Return index of match or null. var matchAt = function(re, s, offset) { var res = s.slice(offset).match(re); if (res) { return offset + res.index; } else { return null; } }; // Convert tabs to spaces on each line using a 4-space tab stop. var detabLine = function(text) { if (text.indexOf('\t') == -1) { return text; } else { var lastStop = 0; return text.replace(reAllTab, function(match, offset) { var result = ' '.slice((offset - lastStop) % 4); lastStop = offset + 1; return result; }); } }; // INLINE PARSER // These are methods of an InlineParser object, defined below. // An InlineParser keeps track of a subject (a string to be // parsed) and a position in that subject. // If re matches at current position in the subject, advance // position in subject and return the match; otherwise return null. var match = function(re) { var match = re.exec(this.subject.slice(this.pos)); if (match) { this.pos += match.index + match[0].length; return match[0]; } else { return null; } }; // Returns the character at the current subject position, or null if // there are no more characters. var peek = function() { return this.subject[this.pos] || null; }; // Parse zero or more space characters, including at most one newline var spnl = function() { this.match(/^ *(?:\n *)?/); return 1; }; // All of the parsers below try to match something at the current position // in the subject. If they succeed in matching anything, they // push an inline element onto the 'inlines' list. They return the // number of characters parsed (possibly 0). // Attempt to parse backticks, adding either a backtick code span or a // literal sequence of backticks to the 'inlines' list. var parseBackticks = function(inlines) { var startpos = this.pos; var ticks = this.match(/^`+/); if (!ticks) { return 0; } var afterOpenTicks = this.pos; var foundCode = false; var match; while (!foundCode && (match = this.match(/`+/m))) { if (match == ticks) { inlines.push({ t: 'Code', c: this.subject.slice(afterOpenTicks, this.pos - ticks.length) .replace(/[ \n]+/g,' ') .trim() }); return (this.pos - startpos); } } // If we got here, we didn't match a closing backtick sequence. inlines.push({ t: 'Str', c: ticks }); this.pos = afterOpenTicks; return (this.pos - startpos); }; // Parse a backslash-escaped special character, adding either the escaped // character, a hard line break (if the backslash is followed by a newline), // or a literal backslash to the 'inlines' list. var parseEscaped = function(inlines) { var subj = this.subject, pos = this.pos; if (subj[pos] === '\\') { if (subj[pos + 1] === '\n') { inlines.push({ t: 'Hardbreak' }); this.pos = this.pos + 2; return 2; } else if (reEscapable.test(subj[pos + 1])) { inlines.push({ t: 'Str', c: subj[pos + 1] }); this.pos = this.pos + 2; return 2; } else { this.pos++; inlines.push({t: 'Str', c: '\\'}); return 1; } } else { return 0; } }; // Attempt to parse an autolink (URL or email in pointy brackets). var parseAutolink = function(inlines) { var m; var dest; 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 dest = m.slice(1,-1); inlines.push({ t: 'Link', label: [{ t: 'Str', c: dest }], destination: 'mailto:' + dest }); return m.length; } 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))) { dest = m.slice(1,-1); inlines.push({ t: 'Link', label: [{ t: 'Str', c: dest }], destination: dest }); return m.length; } else { return 0; } }; // Attempt to parse a raw HTML tag. var parseHtmlTag = function(inlines) { var m = this.match(reHtmlTag); if (m) { inlines.push({ t: 'Html', c: m }); return m.length; } else { return 0; } }; // Scan a sequence of characters == c, and return information about // the number of delimiters and whether they are positioned such that // they can open and/or close emphasis or strong emphasis. A utility // function for strong/emph parsing. var scanDelims = function(c) { var numdelims = 0; var first_close_delims = 0; var char_before, char_after; var startpos = this.pos; char_before = this.pos === 0 ? '\n' : this.subject[this.pos - 1]; while (this.peek() === c) { numdelims++; this.pos++; } char_after = this.peek() || '\n'; var can_open = numdelims > 0 && numdelims <= 3 && !(/\s/.test(char_after)); var can_close = numdelims > 0 && numdelims <= 3 && !(/\s/.test(char_before)); if (c === '_') { can_open = can_open && !((/[a-z0-9]/i).test(char_before)); can_close = can_close && !((/[a-z0-9]/i).test(char_after)); } this.pos = startpos; return { numdelims: numdelims, can_open: can_open, can_close: can_close }; }; // Attempt to parse emphasis or strong emphasis in an efficient way, // with no backtracking. var parseEmphasis = function(inlines) { var startpos = this.pos; var c ; var first_close = 0; var nxt = this.peek(); if (nxt == '*' || nxt == '_') { c = nxt; } else { return 0; } var numdelims; var delimpos; // Get opening delimiters. res = this.scanDelims(c); numdelims = res.numdelims; this.pos += numdelims; // We provisionally add a literal string. If we match appropriate // closing delimiters, we'll change this to Strong or Emph. inlines.push({t: 'Str', c: this.subject.substr(this.pos - numdelims, numdelims)}); // Record the position of this opening delimiter: delimpos = inlines.length - 1; if (!res.can_open || numdelims === 0) { return 0; } var first_close_delims = 0; switch (numdelims) { case 1: // we started with * or _ while (true) { res = this.scanDelims(c); if (res.numdelims >= 1 && res.can_close) { this.pos += 1; // Convert the inline at delimpos, currently a string with the delim, // into an Emph whose contents are the succeeding inlines inlines[delimpos].t = 'Emph'; inlines[delimpos].c = inlines.slice(delimpos + 1); inlines.splice(delimpos + 1); break; } else { if (this.parseInline(inlines) === 0) { break; } } } return (this.pos - startpos); case 2: // We started with ** or __ while (true) { res = this.scanDelims(c); if (res.numdelims >= 2 && res.can_close) { this.pos += 2; inlines[delimpos].t = 'Strong'; inlines[delimpos].c = inlines.slice(delimpos + 1); inlines.splice(delimpos + 1); break; } else { if (this.parseInline(inlines) === 0) { break; } } } return (this.pos - startpos); case 3: // We started with *** or ___ while (true) { res = this.scanDelims(c); if (res.numdelims >= 1 && res.numdelims <= 3 && res.can_close && res.numdelims != first_close_delims) { if (first_close_delims === 1 && numdelims > 2) { res.numdelims = 2; } else if (first_close_delims === 2) { res.numdelims = 1; } else if (res.numdelims === 3) { // If we opened with ***, then we interpret *** as ** followed by * // giving us res.numdelims = 1; } this.pos += res.numdelims; if (first_close > 0) { // if we've already passed the first closer: inlines[delimpos].t = first_close_delims === 1 ? 'Strong' : 'Emph'; inlines[delimpos].c = [ { t: first_close_delims === 1 ? 'Emph' : 'Strong', c: inlines.slice(delimpos + 1, first_close)} ].concat(inlines.slice(first_close + 1)); inlines.splice(delimpos + 1); break; } else { // this is the first closer; for now, add literal string; // we'll change this when he hit the second closer inlines.push({t: 'Str', c: this.subject.slice(this.pos - res.numdelims, this.pos) }); first_close = inlines.length - 1; first_close_delims = res.numdelims; } } else { // parse another inline element, til we hit the end if (this.parseInline(inlines) === 0) { break; } } } return (this.pos - startpos); default: return res; } return 0; }; // Attempt to parse link title (sans quotes), returning the string // or null if no match. var parseLinkTitle = function() { var title = this.match(reLinkTitle); if (title) { // chop off quotes from title and unescape: return unescape(title.substr(1, title.length - 2)); } else { return null; } }; // Attempt to parse link destination, returning the string or // null if no match. var parseLinkDestination = function() { var res = this.match(reLinkDestinationBraces); if (res) { // chop off surrounding <..>: return unescape(res.substr(1, res.length - 2)); } else { res = this.match(reLinkDestination); if (res !== null) { return unescape(res); } else { return null; } } }; // Attempt to parse a link label, returning number of characters parsed. var parseLinkLabel = function() { if (this.peek() != '[') { return 0; } var startpos = this.pos; var nest_level = 0; if (this.label_nest_level > 0) { // If we've already checked to the end of this subject // for a label, even with a different starting [, we // know we won't find one here and we can just return. // This avoids lots of backtracking. // Note: nest level 1 would be: [foo [bar] // nest level 2 would be: [foo [bar [baz] this.label_nest_level--; return 0; } this.pos++; // advance past [ var c; while ((c = this.peek()) && (c != ']' || nest_level > 0)) { switch (c) { case '`': this.parseBackticks([]); break; case '<': this.parseAutolink([]) || this.parseHtmlTag([]) || this.parseString([]); break; case '[': // nested [] nest_level++; this.pos++; break; case ']': // nested [] nest_level--; this.pos++; break; case '\\': this.parseEscaped([]); break; default: this.parseString([]); } } if (c === ']') { this.label_nest_level = 0; this.pos++; // advance past ] return this.pos - startpos; } else { if (!c) { this.label_nest_level = nest_level; } this.pos = startpos; return 0; } }; // Parse raw link label, including surrounding [], and return // inline contents. (Note: this is not a method of InlineParser.) var parseRawLabel = function(s) { // note: parse without a refmap; we don't want links to resolve // in nested brackets! return new InlineParser().parse(s.substr(1, s.length - 2), {}); }; // Attempt to parse a link. If successful, add the link to // inlines. var parseLink = function(inlines) { var startpos = this.pos; var reflabel; var n; var dest; var title; n = this.parseLinkLabel(); if (n === 0) { return 0; } var afterlabel = this.pos; var rawlabel = this.subject.substr(startpos, n); // if we got this far, we've parsed a label. // Try to parse an explicit link: [label](url "title") if (this.peek() == '(') { this.pos++; if (this.spnl() && ((dest = this.parseLinkDestination()) !== null) && this.spnl() && // make sure there's a space before the title: (/^\s/.test(this.subject[this.pos - 1]) && (title = this.parseLinkTitle() || '') || true) && this.spnl() && this.match(/^\)/)) { inlines.push({ t: 'Link', destination: dest, title: title, label: parseRawLabel(rawlabel) }); return this.pos - startpos; } else { this.pos = startpos; return 0; } } // If we're here, it wasn't an explicit link. Try to parse a reference link. // first, see if there's another label var savepos = this.pos; this.spnl(); var beforelabel = this.pos; n = this.parseLinkLabel(); if (n == 2) { // empty second label reflabel = rawlabel; } else if (n > 0) { reflabel = this.subject.slice(beforelabel, beforelabel + n); } else { this.pos = savepos; reflabel = rawlabel; } // lookup rawlabel in refmap var link = this.refmap[normalizeReference(reflabel)]; if (link) { inlines.push({t: 'Link', destination: link.destination, title: link.title, label: parseRawLabel(rawlabel) }); return this.pos - startpos; } else { this.pos = startpos; return 0; } // Nothing worked, rewind: this.pos = startpos; return 0; }; // Attempt to parse an entity, adding to inlines if successful. var parseEntity = function(inlines) { var m; if ((m = this.match(/^&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});/i))) { inlines.push({ t: 'Entity', c: m }); return m.length; } else { return 0; } }; // Parse a run of ordinary characters, or a single character with // a special meaning in markdown, as a plain string, adding to inlines. var parseString = function(inlines) { var m; if ((m = this.match(reMain))) { inlines.push({ t: 'Str', c: m }); return m.length; } else { return 0; } }; // Parse a newline. If it was preceded by two spaces, return a hard // line break; otherwise a soft line break. var parseNewline = function(inlines) { if (this.peek() == '\n') { this.pos++; var last = inlines[inlines.length - 1]; if (last && last.t == 'Str' && last.c.slice(-2) == ' ') { last.c = last.c.replace(/ *$/,''); inlines.push({ t: 'Hardbreak' }); } else { if (last && last.t == 'Str' && last.c.slice(-1) == ' ') { last.c = last.c.slice(0, -1); } inlines.push({ t: 'Softbreak' }); } return 1; } else { return 0; } }; // Attempt to parse an image. If the opening '!' is not followed // by a link, add a literal '!' to inlines. var parseImage = function(inlines) { if (this.match(/^!/)) { var n = this.parseLink(inlines); if (n === 0) { inlines.push({ t: 'Str', c: '!' }); return 1; } else if (inlines[inlines.length - 1] && inlines[inlines.length - 1].t == 'Link') { inlines[inlines.length - 1].t = 'Image'; return n+1; } else { throw "Shouldn't happen"; } } else { return 0; } }; // Attempt to parse a link reference, modifying refmap. var parseReference = function(s, refmap) { this.subject = s; this.pos = 0; var rawlabel; var dest; var title; var matchChars; var startpos = this.pos; var match; // label: matchChars = this.parseLinkLabel(); if (matchChars === 0) { return 0; } else { rawlabel = this.subject.substr(0, matchChars); } // colon: if (this.peek() === ':') { this.pos++; } else { this.pos = startpos; return 0; } // link url this.spnl(); dest = this.parseLinkDestination(); if (dest === null || dest.length === 0) { this.pos = startpos; return 0; } var beforetitle = this.pos; this.spnl(); title = this.parseLinkTitle(); if (title === null) { title = ''; // rewind before spaces this.pos = beforetitle; } // make sure we're at line end: if (this.match(/^ *(?:\n|$)/) === null) { this.pos = startpos; return 0; } var normlabel = normalizeReference(rawlabel); if (!refmap[normlabel]) { refmap[normlabel] = { destination: dest, title: title }; } return this.pos - startpos; }; // Parse the next inline element in subject, advancing subject position // and adding the result to 'inlines'. var parseInline = function(inlines) { var c = this.peek(); var res; switch(c) { case '\n': res = this.parseNewline(inlines); break; case '\\': res = this.parseEscaped(inlines); break; case '`': res = this.parseBackticks(inlines); break; case '*': case '_': res = this.parseEmphasis(inlines); break; case '[': res = this.parseLink(inlines); break; case '!': res = this.parseImage(inlines); break; case '<': res = this.parseAutolink(inlines) || this.parseHtmlTag(inlines); break; case '&': res = this.parseEntity(inlines); break; default: } return res || this.parseString(inlines); }; // Parse s as a list of inlines, using refmap to resolve references. var parseInlines = function(s, refmap) { this.subject = s; this.pos = 0; this.refmap = refmap || {}; var inlines = []; while (this.parseInline(inlines)) ; return inlines; }; // The InlineParser object. function InlineParser(){ return { subject: '', label_nest_level: 0, // used by parseLinkLabel method pos: 0, refmap: {}, match: match, peek: peek, spnl: spnl, parseBackticks: parseBackticks, parseEscaped: parseEscaped, parseAutolink: parseAutolink, parseHtmlTag: parseHtmlTag, scanDelims: scanDelims, parseEmphasis: parseEmphasis, parseLinkTitle: parseLinkTitle, parseLinkDestination: parseLinkDestination, parseLinkLabel: parseLinkLabel, parseLink: parseLink, parseEntity: parseEntity, parseString: parseString, parseNewline: parseNewline, parseImage: parseImage, parseReference: parseReference, parseInline: parseInline, parse: parseInlines }; } // DOC PARSER // These are methods of a DocParser object, defined below. var makeBlock = function(tag, start_line, start_column) { return { t: tag, open: true, last_line_blank: false, start_line: start_line, start_column: start_column, end_line: start_line, children: [], parent: null, // string_content is formed by concatenating strings, in finalize: string_content: "", strings: [], inline_content: [] }; }; // Returns true if parent block can contain child block. var canContain = function(parent_type, child_type) { return ( parent_type == 'Document' || parent_type == 'BlockQuote' || parent_type == 'ListItem' || (parent_type == 'List' && child_type == 'ListItem') ); }; // Returns true if block type can accept lines of text. var acceptsLines = function(block_type) { return ( block_type == 'Paragraph' || block_type == 'IndentedCode' || block_type == 'FencedCode' ); }; // Returns true if block ends with a blank line, descending if needed // into lists and sublists. var endsWithBlankLine = function(block) { if (block.last_line_blank) { return true; } if ((block.t == 'List' || block.t == 'ListItem') && block.children.length > 0) { return endsWithBlankLine(block.children[block.children.length - 1]); } else { return false; } }; // Break out of all containing lists, resetting the tip of the // document to the parent of the highest list, and finalizing // all the lists. (This is used to implement the "two blank lines // break of of all lists" feature.) var breakOutOfLists = function(block, line_number) { var b = block; var last_list = null; do { if (b.t === 'List') { last_list = b; } b = b.parent; } while (b); if (last_list) { while (block != last_list) { this.finalize(block, line_number); block = block.parent; } this.finalize(last_list, line_number); this.tip = last_list.parent; } }; // Add a line to the block at the tip. We assume the tip // can accept lines -- that check should be done before calling this. var addLine = function(ln, offset) { var s = ln.slice(offset); if (!(this.tip.open)) { throw({ msg: "Attempted to add line (" + ln + ") to closed container." }); } this.tip.strings.push(s); }; // Add block of type tag as a child of the tip. If the tip can't // accept children, close and finalize it and try its parent, // and so on til we find a block that can accept children. var addChild = function(tag, line_number, offset) { while (!canContain(this.tip.t, tag)) { this.finalize(this.tip, line_number); } var column_number = offset + 1; // offset 0 = column 1 var newBlock = makeBlock(tag, line_number, column_number); this.tip.children.push(newBlock); newBlock.parent = this.tip; this.tip = newBlock; return newBlock; }; // Parse a list marker and return data on the marker (type, // start, delimiter, bullet character, padding) or null. var parseListMarker = function(ln, offset) { var rest = ln.slice(offset); var match; var spaces_after_marker; var data = {}; if (rest.match(reHrule)) { return null; } if ((match = rest.match(/^[*+-]( +|$)/))) { spaces_after_marker = match[1].length; data.type = 'Bullet'; data.bullet_char = match[0][0]; } else if ((match = rest.match(/^(\d+)([.)])( +|$)/))) { spaces_after_marker = match[3].length; data.type = 'Ordered'; data.start = parseInt(match[1]); data.delimiter = match[2]; } else { return null; } var blank_item = match[0].length === rest.length; if (spaces_after_marker >= 5 || spaces_after_marker < 1 || blank_item) { data.padding = match[0].length - spaces_after_marker + 1; } else { data.padding = match[0].length; } return data; }; // Returns true if the two list items are of the same type, // with the same delimiter and bullet character. This is used // in agglomerating list items into lists. var listsMatch = function(list_data, item_data) { return (list_data.type === item_data.type && list_data.delimiter === item_data.delimiter && list_data.bullet_char === item_data.bullet_char); }; // Analyze a line of text and update the document appropriately. // We parse markdown text by calling this on each line of input, // then finalizing the document. var incorporateLine = function(ln, line_number) { var all_matched = true; var last_child; var first_nonspace; var offset = 0; var match; var data; var blank; var indent; var last_matched_container; var i; var CODE_INDENT = 4; var container = this.doc; var oldtip = this.tip; // Convert tabs to spaces: ln = detabLine(ln); // For each containing block, try to parse the associated line start. // Bail out on failure: container will point to the last matching block. // Set all_matched to false if not all containers match. while (container.children.length > 0) { last_child = container.children[container.children.length - 1]; if (!last_child.open) { break; } container = last_child; match = matchAt(/[^ ]/, ln, offset); if (match === null) { first_nonspace = ln.length; blank = true; } else { first_nonspace = match; blank = false; } indent = first_nonspace - offset; switch (container.t) { case 'BlockQuote': var matched = indent <= 3 && ln[first_nonspace] === '>'; if (matched) { offset = first_nonspace + 1; if (ln[offset] === ' ') { offset++; } } else { all_matched = false; } break; case 'ListItem': if (indent >= container.list_data.marker_offset + container.list_data.padding) { offset += container.list_data.marker_offset + container.list_data.padding; } else if (blank) { offset = first_nonspace; } else { all_matched = false; } break; case 'IndentedCode': if (indent >= CODE_INDENT) { offset += CODE_INDENT; } else if (blank) { offset = first_nonspace; } else { all_matched = false; } break; case 'ATXHeader': case 'SetextHeader': case 'HorizontalRule': // a header can never container > 1 line, so fail to match: all_matched = false; break; case 'FencedCode': // skip optional spaces of fence offset i = container.fence_offset; while (i > 0 && ln[offset] === ' ') { offset++; i--; } break; case 'HtmlBlock': if (blank) { all_matched = false; } break; case 'Paragraph': if (blank) { container.last_line_blank = true; all_matched = false; } break; default: } if (!all_matched) { container = container.parent; // back up to last matching block break; } } last_matched_container = container; // This function is used to finalize and close any unmatched // blocks. We aren't ready to do this now, because we might // have a lazy paragraph continuation, in which case we don't // want to close unmatched blocks. So we store this closure for // use later, when we have more information. var closeUnmatchedBlocks = function(mythis) { // finalize any blocks not matched while (!already_done && oldtip != last_matched_container) { mythis.finalize(oldtip, line_number); oldtip = oldtip.parent; } var already_done = true; }; // Check to see if we've hit 2nd blank line; if so break out of list: if (blank && container.last_line_blank) { this.breakOutOfLists(container, line_number); } // Unless last matched container is a code block, try new container starts, // adding children to the last matched container: while (container.t != 'FencedCode' && container.t != 'IndentedCode' && container.t != 'HtmlBlock' && // this is a little performance optimization: matchAt(/^[ #`~*+_=<>0-9-]/,ln,offset) !== null) { match = matchAt(/[^ ]/, ln, offset); if (match === null) { first_nonspace = ln.length; blank = true; } else { first_nonspace = match; blank = false; } indent = first_nonspace - offset; if (indent >= CODE_INDENT) { // indented code if (this.tip.t != 'Paragraph' && !blank) { offset += CODE_INDENT; closeUnmatchedBlocks(this); container = this.addChild('IndentedCode', line_number, offset); } else { // indent > 4 in a lazy paragraph continuation break; } } else if (ln[first_nonspace] === '>') { // blockquote offset = first_nonspace + 1; // optional following space if (ln[offset] === ' ') { offset++; } closeUnmatchedBlocks(this); container = this.addChild('BlockQuote', line_number, offset); } else if ((match = ln.slice(first_nonspace).match(/^#{1,6}(?: +|$)/))) { // ATX header offset = first_nonspace + match[0].length; closeUnmatchedBlocks(this); container = this.addChild('ATXHeader', line_number, first_nonspace); container.level = match[0].trim().length; // number of #s // remove trailing ###s: container.strings = [ln.slice(offset).replace(/(?:(\\#) *#*| *#+) *$/,'$1')]; break; } else if ((match = ln.slice(first_nonspace).match(/^`{3,}(?!.*`)|^~{3,}(?!.*~)/))) { // fenced code block var fence_length = match[0].length; closeUnmatchedBlocks(this); container = this.addChild('FencedCode', line_number, first_nonspace); container.fence_length = fence_length; container.fence_char = match[0][0]; container.fence_offset = first_nonspace - offset; offset = first_nonspace + fence_length; break; } else if (matchAt(reHtmlBlockOpen, ln, first_nonspace) !== null) { // html block closeUnmatchedBlocks(this); container = this.addChild('HtmlBlock', line_number, first_nonspace); // note, we don't adjust offset because the tag is part of the text break; } else if (container.t == 'Paragraph' && container.strings.length === 1 && ((match = ln.slice(first_nonspace).match(/^(?:=+|-+) *$/)))) { // setext header line closeUnmatchedBlocks(this); container.t = 'SetextHeader'; // convert Paragraph to SetextHeader container.level = match[0][0] === '=' ? 1 : 2; offset = ln.length; } else if (matchAt(reHrule, ln, first_nonspace) !== null) { // hrule closeUnmatchedBlocks(this); container = this.addChild('HorizontalRule', line_number, first_nonspace); offset = ln.length - 1; break; } else if ((data = parseListMarker(ln, first_nonspace))) { // list item closeUnmatchedBlocks(this); data.marker_offset = indent; offset = first_nonspace + data.padding; // add the list if needed if (container.t !== 'List' || !(listsMatch(container.list_data, data))) { container = this.addChild('List', line_number, first_nonspace); container.list_data = data; } // add the list item container = this.addChild('ListItem', line_number, first_nonspace); container.list_data = data; } else { break; } if (acceptsLines(container.t)) { // if it's a line container, it can't contain other containers break; } } // What remains at the offset is a text line. Add the text to the // appropriate container. match = matchAt(/[^ ]/, ln, offset); if (match === null) { first_nonspace = ln.length; blank = true; } else { first_nonspace = match; blank = false; } indent = first_nonspace - offset; // First check for a lazy paragraph continuation: if (this.tip !== last_matched_container && !blank && this.tip.t == 'Paragraph' && this.tip.strings.length > 0) { // lazy paragraph continuation this.last_line_blank = false; this.addLine(ln, offset); } else { // not a lazy continuation // finalize any blocks not matched closeUnmatchedBlocks(this); // Block quote lines are never blank as they start with > // and we don't count blanks in fenced code for purposes of tight/loose // lists or breaking out of lists. We also don't set last_line_blank // on an empty list item. container.last_line_blank = blank && !(container.t == 'BlockQuote' || container.t == 'FencedCode' || (container.t == 'ListItem' && container.children.length === 0 && container.start_line == line_number)); var cont = container; while (cont.parent) { cont.parent.last_line_blank = false; cont = cont.parent; } switch (container.t) { case 'IndentedCode': case 'HtmlBlock': this.addLine(ln, offset); break; case 'FencedCode': // check for closing code fence: match = (indent <= 3 && ln[first_nonspace] == container.fence_char && ln.slice(first_nonspace).match(/^(?:`{3,}|~{3,})(?= *$)/)); if (match && match[0].length >= container.fence_length) { // don't add closing fence to container; instead, close it: this.finalize(container, line_number); } else { this.addLine(ln, offset); } break; case 'ATXHeader': case 'SetextHeader': case 'HorizontalRule': // nothing to do; we already added the contents. break; default: if (acceptsLines(container.t)) { this.addLine(ln, first_nonspace); } else if (blank) { // do nothing } else if (container.t != 'HorizontalRule' && container.t != 'SetextHeader') { // create paragraph container for line container = this.addChild('Paragraph', line_number, first_nonspace); this.addLine(ln, first_nonspace); } else { console.log("Line " + line_number.toString() + " with container type " + container.t + " did not match any condition."); } } } }; // Finalize a block. Close it and do any necessary postprocessing, // e.g. creating string_content from strings, setting the 'tight' // or 'loose' status of a list, and parsing the beginnings // of paragraphs for reference definitions. Reset the tip to the // parent of the closed block. var finalize = function(block, line_number) { var pos; // don't do anything if the block is already closed if (!block.open) { return 0; } block.open = false; if (line_number > block.start_line) { block.end_line = line_number - 1; } else { block.end_line = line_number; } switch (block.t) { case 'Paragraph': block.string_content = block.strings.join('\n').replace(/^ */m,''); // try parsing the beginning as link reference definitions: while (block.string_content[0] === '[' && (pos = this.inlineParser.parseReference(block.string_content, this.refmap))) { block.string_content = block.string_content.slice(pos); if (isBlank(block.string_content)) { block.t = 'ReferenceDef'; break; } } break; case 'ATXHeader': case 'SetextHeader': case 'HtmlBlock': block.string_content = block.strings.join('\n'); break; case 'IndentedCode': block.string_content = block.strings.join('\n').replace(/(\n *)*$/,'\n'); break; case 'FencedCode': // first line becomes info string block.info = unescape(block.strings[0].trim()); if (block.strings.length == 1) { block.string_content = ''; } else { block.string_content = block.strings.slice(1).join('\n') + '\n'; } break; case 'List': block.tight = true; // tight by default var numitems = block.children.length; var i = 0; while (i < numitems) { var item = block.children[i]; // check for non-final list item ending with blank line: var last_item = i == numitems - 1; if (endsWithBlankLine(item) && !last_item) { block.tight = false; break; } // recurse into children of list item, to see if there are // spaces between any of them: var numsubitems = item.children.length; var j = 0; while (j < numsubitems) { var subitem = item.children[j]; var last_subitem = j == numsubitems - 1; if (endsWithBlankLine(subitem) && !(last_item && last_subitem)) { block.tight = false; break; } j++; } i++; } break; default: break; } this.tip = block.parent || this.top; }; // Walk through a block & children recursively, parsing string content // into inline content where appropriate. var processInlines = function(block) { switch(block.t) { case 'Paragraph': case 'SetextHeader': case 'ATXHeader': block.inline_content = this.inlineParser.parse(block.string_content.trim(), this.refmap); block.string_content = ""; break; default: break; } if (block.children) { for (var i = 0; i < block.children.length; i++) { this.processInlines(block.children[i]); } } }; // The main parsing function. Returns a parsed document AST. var parse = function(input) { this.doc = makeBlock('Document', 1, 1); this.tip = this.doc; this.refmap = {}; var lines = input.replace(/\n$/,'').split(/\r\n|\n|\r/); var len = lines.length; for (var i = 0; i < len; i++) { this.incorporateLine(lines[i], i+1); } while (this.tip) { this.finalize(this.tip, len - 1); } this.processInlines(this.doc); return this.doc; }; // The DocParser object. function DocParser(){ return { doc: makeBlock('Document', 1, 1), tip: this.doc, refmap: {}, inlineParser: new InlineParser(), breakOutOfLists: breakOutOfLists, addLine: addLine, addChild: addChild, incorporateLine: incorporateLine, finalize: finalize, processInlines: processInlines, parse: parse }; } // HTML RENDERER // Helper function to produce content in a pair of HTML tags. var inTags = function(tag, attribs, contents, selfclosing) { var result = '<' + tag; if (attribs) { var i = 0; var attrib; while ((attrib = attribs[i]) !== undefined) { result = result.concat(' ', attrib[0], '="', attrib[1], '"'); i++; } } if (contents) { result = result.concat('>', contents, ''); } else if (selfclosing) { result = result + ' />'; } else { result = result.concat('>'); } return result; }; // Render an inline element as HTML. var renderInline = function(inline) { var attrs; switch (inline.t) { case 'Str': return this.escape(inline.c); case 'Softbreak': return this.softbreak; case 'Hardbreak': return inTags('br',[],"",true) + '\n'; case 'Emph': return inTags('em', [], this.renderInlines(inline.c)); case 'Strong': return inTags('strong', [], this.renderInlines(inline.c)); case 'Html': return inline.c; case 'Entity': return inline.c; case 'Link': attrs = [['href', this.escape(inline.destination, true)]]; if (inline.title) { attrs.push(['title', this.escape(inline.title, true)]); } return inTags('a', attrs, this.renderInlines(inline.label)); case 'Image': attrs = [['src', this.escape(inline.destination, true)], ['alt', this.escape(this.renderInlines(inline.label))]]; if (inline.title) { attrs.push(['title', this.escape(inline.title, true)]); } return inTags('img', attrs, "", true); case 'Code': return inTags('code', [], this.escape(inline.c)); default: console.log("Uknown inline type " + inline.t); return ""; } }; // Render a list of inlines. var renderInlines = function(inlines) { var result = ''; for (var i=0; i < inlines.length; i++) { result = result + this.renderInline(inlines[i]); } return result; }; // Render a single block element. var renderBlock = function(block, in_tight_list) { var tag; var attr; var info_words; switch (block.t) { case 'Document': var whole_doc = this.renderBlocks(block.children); return (whole_doc === '' ? '' : whole_doc + '\n'); case 'Paragraph': if (in_tight_list) { return this.renderInlines(block.inline_content); } else { return inTags('p', [], this.renderInlines(block.inline_content)); } break; case 'BlockQuote': var filling = this.renderBlocks(block.children); return inTags('blockquote', [], filling === '' ? this.innersep : this.innersep + this.renderBlocks(block.children) + this.innersep); case 'ListItem': return inTags('li', [], this.renderBlocks(block.children, in_tight_list).trim()); case 'List': tag = block.list_data.type == 'Bullet' ? 'ul' : 'ol'; attr = (!block.list_data.start || block.list_data.start == 1) ? [] : [['start', block.list_data.start.toString()]]; return inTags(tag, attr, this.innersep + this.renderBlocks(block.children, block.tight) + this.innersep); case 'ATXHeader': case 'SetextHeader': tag = 'h' + block.level; return inTags(tag, [], this.renderInlines(block.inline_content)); case 'IndentedCode': return inTags('pre', [], inTags('code', [], this.escape(block.string_content))); case 'FencedCode': info_words = block.info.split(/ +/); attr = info_words.length === 0 || info_words[0].length === 0 ? [] : [['class','language-' + this.escape(info_words[0],true)]]; return inTags('pre', [], inTags('code', attr, this.escape(block.string_content))); case 'HtmlBlock': return block.string_content; case 'ReferenceDef': return ""; case 'HorizontalRule': return inTags('hr',[],"",true); default: console.log("Uknown block type " + block.t); return ""; } }; // Render a list of block elements, separated by this.blocksep. var renderBlocks = function(blocks, in_tight_list) { var result = []; for (var i=0; i < blocks.length; i++) { if (blocks[i].t !== 'ReferenceDef') { result.push(this.renderBlock(blocks[i], in_tight_list)); } } return result.join(this.blocksep); }; // The HtmlRenderer object. function HtmlRenderer(){ return { // default options: blocksep: '\n', // space between blocks innersep: '\n', // space between block container tag and contents softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML // set to "
" to make them hard breaks // set to " " if you want to ignore line wrapping in source escape: function(s, preserve_entities) { if (preserve_entities) { return s.replace(/[&](?![#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)/gi,'&') .replace(/[<]/g,'<') .replace(/[>]/g,'>') .replace(/["]/g,'"'); } else { return s.replace(/[&]/g,'&') .replace(/[<]/g,'<') .replace(/[>]/g,'>') .replace(/["]/g,'"'); } }, renderInline: renderInline, renderInlines: renderInlines, renderBlock: renderBlock, renderBlocks: renderBlocks, render: renderBlock }; } exports.DocParser = DocParser; exports.HtmlRenderer = HtmlRenderer; })(typeof exports === 'undefined' ? this.stmd = {} : exports); ================================================ FILE: web/index.html ================================================ XYscope

XYscope

v 3.0.0
cc teddavis.org 2017 – 2023

Processing library to render vector graphics on vector displays.

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!).

teddavis.org/xyscope
github.com/ffd8/xyscope

Table of Contents

Installation

Add via Processing's Library Manager:

  • Sketch menu » Import Library... » Add Library... (v4 Manage Libraries...)
  • Search for 'XYscope' and click Install.
  • Search for 'Minim' and click Install.

This library relies on and is infinitely thankful for Minim!
Search for and add the following libraries for various examples:
Geomerative, OpenCV for Processing, openkinect, video, syphon

Add manually, download latest release and expand into ~/Processing/libraries:

Audio Interfaces

< $5
At the very least you can use your computer's headphone jack with an 1/8" to RCA cable. However you'll soon want to get a DC-Coupled audio interface for a cleaner and more stable visual (not wobbling/centering constantly).

< $20
For workshops I like to use some variant of the 48Khz Delock 61645 or 96Khz Delock 63926 – 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 version.

> $150+
Many of us in the community found the MOTU Ultralite Mk3 Hybrid (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).

List

Check which interface is plugged in and how to reference it.

xy.getMixerInfo(); // lists available audio devices

Select

By default XYscope uses the system settings audio-output, however we can specify a custom sound card (Digital Analog Converter – DAC) and sample rate.

xy = new XYscope(this); // system settings soundcard 
xy = new XYscope(this, "MOTU 1_2"); // specify a soundcard/channel to use 
xy = new XYscope(this, 96000); // custom sample rate ( > finer detail if card supports) 
xy = new XYscope(this, "MOTU 1_2", 96000); // custom soundcard, custom sample rate 

Or select a previous XYscope instance as the output for additive-synthesis:

xy2 = new XYscope(this, xy.outXY); // send 2nd instance to 1st

Aggregate Device

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:

  • Press CMD + Spacebar (opens Spotlight Search)
  • Type Audio Midi Setup, press ENTER
  • Click + in lower left, select Create Aggregate Device
  • Tick checkbox of your multi-channel audio device
  • Click Configure Speakers... in lower right
  • Set configuration to Stereo, select channels desired
  • Rename to something like: MOTU 1_2
  • Repeat above for each stereo pair, ie, MOTU 3_4, MOTU 5_6...
  • They can now be selected by name when initializing XYscope!

Multi-Channel Output Device

Within Audio Midi Setup, you can Create Multi-Output Device to send your output to multiple devices, ie. Blackhole + DAC + Speakers, so you can view it virtually, on analog device, and hear it.

  • Press CMD + Spacebar (opens Spotlight Search)
  • Type Audio Midi Setup, press ENTER
  • Click + in lower left, select Create Multi-Channel Device
  • Tick checkbox of best device first, then select additional
  • Set sample rate to highest available, 48000 or 96000+
  • Rename for clarity, ie DAC + SPEAKERS or BLACKHOLE + SPEAKERS.

Vector Displays

Now we need a vector display to see our glowing output!

Virtual

Oscilloscope by Hansi Raber (adopting m1el's woscope '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 (MacOS) or VB-CABLE (Windows) to re-route your system audio to a virtual source for Oscilloscope to render it.

Analog Oscilloscope

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 RCA to BNC 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).

X-Y Monitor

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.

Vectrex

A vector-graphics video game system of the 1980s, these amazing 9" displays can be very carefully modified (CAREFUL - at own risk) to override (on-demand) the videogame control of the monitor's XYZ inputs. It's ideal to use switching jacks so videogames still works when cables are unplugged. You'll also want to apply the SPOT KILLER MOD, but BE SURE to apply an appropriately high-voltage rated switch, so it can be toggled on and off.

XYscope has a special mode when using a Vectrex for the aspect ratio. See example vextrex within 5_displays, but the main notes are:

/* within setup() */
xy.vectrex(90); // -90/90 for landscape, 0 for portrait

// optionally z-axis for blanking
xy.z("MOTU 3-4"); // use custom 3rd channel for z-axis
//xy.zRange(.5, 0); // set min/max values for beam on/off

Laser

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 dac_ilda adaptor. 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:

/* within setup() */
// only stereo pairs in processing, so it's broken to R, GB
// incase 2nd channel of R pair is useful for blanking/etc.
xy.laser("MOTU 3-4", "MOTU 5-6"); //xy.laser(mixerR, mixerGB);

/* within setup() or draw() */
// RGB waveforms have own freq, so we can them out of sync
xy.strokeFreq(50.05, 50.1, 50.2);
  
xy.strokeDash(8); // optional dashes in the RGB stroke

xy.stroke(0, 255, 255); // set RGB stroke before shapes (0-255)

xy.limitPath(0); // avoid drawing any forms beyond view

Getting Started

Our basic template for an XYscope sketch involves:

import ddf.minim.*; // minim req to gen audio
import xyscope.*;   // import XYscope
XYscope xy;         // create XYscope instance
 
void setup(){ 
  size(512, 512); // window size, optionally add P3D
 
  xy = new XYscope(this, ""); // define XYscope instance, "custom_dac" optional
  xy.getMixerInfo(); // lists all audio devices
} 
 
void draw(){ 
  background(0); 
  xy.clearWaves();  // clear waves from previous drawing
  
  // draw shapes here
  xy.circle(width/2, height/2, width); // it all began with a circle....
  
  xy.buildWaves(); // build shapes to audio waves
  xy.drawAll();
} 

Additive Synthesis

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.

import ddf.minim.*; // minim req to gen audio
import xyscope.*;   // import XYscope
XYscope xy, xy2;    // create 2x XYscope instances

void setup() {
  size(512, 512); // declares size of output window
  xy = new XYscope(this);
  xy2 = new XYscope(this, xy.outXY); // patch 2nd instance to 1st output
}

void draw() {
  background(0);
  // set main shapes
  xy.clearWaves();
  xy.circle(width/2, height/2, width/2);
  xy.buildWaves();
  xy.drawPath(0, 255, 255); // custom stroke color
  xy.drawXY(); // displays combined output

  // additive synth on 2nd instance of XYscope
  xy2.clearWaves();
  xy2.freq(mouseX); // without speakers, test really high freqs!
  //xy2.amp(.2); // experiment with high freq, low amp modulation
  xy2.circle(width/2, height/2, mouseY);
  xy2.buildWaves();
  xy2.drawPath(255, 255, 0); // custom stroke color
}

Additional tips:

  • No need to stop at just 2, add as many as needed!
  • Ratio between freqs is crucial, diff of +/- .1 animates things.
  • Really low frequencies animate shapes over that path.
  • Really high frequencies display shape made of 2nd shape.
  • Play with position of 2nd shape, from center to corners.

References

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: 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.


Initialize XYscope

xy = new XYscope(this); // system audio settings
xy = new XYscope(this, "custom_dac"); // custom audio card
xy = new XYscope(this, xy_instance.outXY); // another XYscope out
xy = new XYscope(this, sampleRate); // default sampleRate is 41000
xy = new XYscope(this, "custom_dac", sampleRate); // default sampleRate is 41000
xy = new XYscope(this, "custom_dac", sampleRate, bufferSize); // default bufferSize is 512

Z-axis

z()

xy.z("custom_dac"); // set audio out for z-axis (blanking)
xy.z("custom_dac", sampleRate); // add custom sampleRate

zAuto()

// somewhat deprecated.. may return
xy.zAuto(); // get setting for using z-axis
xy.zAuto(boolean); // set auto use of z-axis 

Audio Interface Settings

getMixerInfo()

xy.getMixerInfo(); // list connected audio interfaces

sampleRate()

xy.sampleRate(); // get current sampleRate, default 41000
xy.sampleRate(newRate); // set new sampleRate (int)

bufferSize()

xy.bufferSize(); // get current bufferSize, default 512
xy.bufferSize(newSize); // set  new bufferSize (int)

Waves

resetWaves()

xy.resetWaves(); // fix any sync issues with phase of waves

clearWaves()

xy.clearWaves(); // clear wavetables from any previous drawing

buildWaves()

xy.buildWaves(); // compile drawn shapes into new wavetables/audio
xy.buildWaves(-1); // draw in v1 style
xy.buildWaves(-2); // draw in v2 style
xy.buildWaves(-3); // draw in v3 style

buildX()

xy.buildX(newWave); // build wavetableX with float[] array

buildY()

xy.buildY(newWave); // build wavetableY with float[] array

buildZ()

xy.buildZ(newWave); // build wavetableZ with float[] array

waveSize()

xy.waveSize(); // get current size of wavetables
xy.waveSize(newSize); // set new wavetable size, default is 512 

setWaveforms()

xy.setWaveforms(wfX, wfY, wfZ); // set wavetables with float[] arrays 

Points

wavePoints()

xy.wavePoints(); // get PVector ArrayList of all drawn coordinates (0.0 – 1.0)

steps()

xy.steps(); // get current steps between points, default 24
xy.steps(newVal); // set step multiplier, segments, between points

limitPoints()

xy.limitPoints(); // get number of limited drawing points
xy.limitPoints(newLimit); // set new limit of points for drawing

limitPath()

xy.limitPath(); // get current limit of drawn path
xy.limitPath(newLimit); // avoid drawn vectors beyond border edges (px)

Record Audio

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.

recorderBegin()

xy.recorderBegin(); // will name file "XYscope_timestamp.wav"
xy.recorderBegin("customName"); // set name, timestamp added

recorderEnd()

xy.recorderEnd(); // complete recording

Primitive Shapes

Most 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.

point()

xy.point();
xy.point(x, y);
xy.point(x, y, z);

line()

xy.line();
xy.line(x1, y1, x2, y2);
xy.line(x1, y1, z1, x2, y2, z2);

square()

xy.rectMode(); // default CORNER, CENTER to draw center out

xy.square();
xy.square(x, y, w);

rect()

xy.rectMode(); // default CORNER, CENTER to draw center out

xy.rect();
xy.rect(x, y, w);
xy.rect(x, y, w, h);

circle()

xy.ellipseDetail(); // get current facets of ellipse
xy.ellipseDetail(newVal); // set new count of facets, default 30

xy.circle();
xy.circle(x, y, w);

ellipse()

xy.ellipseDetail(); // get current facets of ellipse
xy.ellipseDetail(newVal); // set new count of facets, default 30

xy.ellipse();
xy.ellipse(x, y, w);
xy.ellipse(x, y, w, h);

complex shape

xy.beginShape();

xy.vertex();
xy.vertex(x, y);
xy.vertex(x, y, z); // if in P3D mode
// ...

xy.endShape();
xy.endShape(CLOSE); // closes form

lissajous()

xy.lissajous();
xy.lissajous(x, y, radius, ratioA, ratioB, phase, resolution);

box()

xy.box();
xy.box(size);
xy.box(rx, ryz);
xy.box(rx, ry, rz);

sphere()

xy.sphere();
xy.sphere(size);
xy.sphere(size, detailXY); // default 24
xy.sphere(size, detailX, detailY); // default 24, 24

ellipsoid()

xy.ellipsoid();
xy.ellipsoid(rx, ry, rz);
xy.ellipsoid(rx, ry, rz, detailXY); // default 24
xy.ellipsoid(rx, ry, rz, detailX, detailY); // default 24, 24

torus()

xy.torus();
xy.torus(radius, tubeRadius);
xy.torus(radius, tubeRadius, detailXY); // default 24
xy.torus(radius, tubeRadius, detailX, detailY); // default 24, 24

Text

text()

xy.text(); // paramless text drawing
xy.text("string", x, y); // use '\n' for multi-line text

textPaths()

Use coordinates of Hershey text for manipulating type!

xy.textPaths("string", x, y); // return 2D Array of PVector coords

textFont()

println(xy.fonts); // print list of avilable Hershey fonts
xy.textFont("fontname"); // set active Hershey font

textSize()

xy.textSize(); // get current textSize
xy.textSize(newSize); // set new textSize

textLeading()

xy.textLeading(); // get current textLeading
xy.textLeading(newSize); // set new textLeading

textAlign()

xy.textAlign(hAlign); // Horz: LEFT (default) / CENTER / RIGHT
xy.textAlign(hAlign, vAlign); // Vert options: TOP / CENTER / BOTTOM

textWidth()

xy.textWidth(int); // from 32 characters, get width of char
xy.textWidth("string"); // get width of text for positioning

Modulation

freq()

Frequency of oscillators

// get
xy.freq(); // get PVector (.x, .y, .z) of freqs
xy.freq().x;   // get frequency of x oscillator 

// set
xy.freq(freqXY);  // default is 50.0 
xy.freq(freqX, freqY); // set x, y frequencies 
xy.freq(freqX, freqY, freqZ); // set x, y, z frequencies 
xy.freq(PVector freqXYZ); // set as PVector 
 
xy.resetWaves(); // occasionally needed if they slip out of phase 

amp()

Amplitude of oscillators

// get
xy.amp();   // get PVector (.x, .y, .z) of amps
xy.amp().x; // get amplitude of x oscillator

// set
xy.amp(ampXY); // default is 1.0 
xy.amp(ampX, ampY); // set x, y to specific amplitudes 
xy.amp(ampX, ampY, ampZ); // set x, y, z to specific amplitudes 
xy.amp(PVector ampXYZ); // set as PVector

pan()

Optionally swap the hard pan of XY rather than physical cables

xy.pan(leftPan, rightPan); // default is -1.0, 1.0

Vectrex

xy.vectrex(0); // 0 for portrait, -90/90 for landscape orientation
xy.vectrex(vw, vh, vamp, vrot); // custom width, height, amp, rotation

xy.vectrexRatio(); // get current ratio
xy.vectrexRatio(newRatio); // set new ratio, default is .82

Laser

xy.laser("dac_Red", "dac_GreenBlue"); // custom 2ch pairs for R, GB

// be cautious of laser galvos, can help smooth graphics
xy.laserLPF(); // get value of laser's LowPassFilter
xy.laserLPF(newLPF); // set mew value of LowPassFilter (0.1 – 20000.0)

// avoid dangerous hotspot, only draw laser if shape is big enough
xy.spotKiller(); // get current min size of drawing for laser
xy.spotKiller(newVal); // set min drawing size to prevent laser hotspot

xy.stroke(r, g, b); // set RGB laser stroke color
xy.stroke(PVector rgb); // set as PVector

xy.strokeFreq(); // get laser RGB channel frequencies
xy.strokeFreq(freqRGB); // set laser RGB freq as group
xy.strokeFreq(freqR, freqG, freqB); // set laser R, G, B separately
xy.strokeFreq(PVector freqRGB); // set as PVector

// some lasers ignore values below __ when setting colors, 
// use this to set lowest point when color mixing
xy.strokeMin(); // get curret min RGB values for laser stroke
xy.strokeMin(minR, minG, minB); // set new min values (0.0 - 255.0)
xy.strokeMin(PVector minPV); // set new min as PVector

xy.strokeWB(wbR, wbG, wbB); // set values needed for white light
xy.strokeWB(PVector wbRGB); // set values as PVector

xy.strokeDash(); // get current dash setting
xy.strokeDash(sdRGB); // set laser dash count for RGB strokes
xy.strokeDash(sdR, sdG, sdB); // set dashes per R, G, B
xy.strokeDash(PVector sdRGB); // set as PVector

Rendering

xy.drawAll(); // all views below 
xy.drawPath(); // shapes being drawn 
xy.drawPoints(); // points along paths of drawing 
xy.drawWaveform(); // drawing as oscillator's waveform 
xy.drawWave(); // waveform over time at frequency 
xy.drawXY(); // simulated xy-mode oscilloscope 

xy.debugView(); // check if debugView active
xy.debugView(boolean); // compare drawXY() to drawWaveform()

Vars

Lastly, many variables within XYscope are public for your vector hacking needs.

shapes; // XYShapeList, processed by buildWaves()
minim, minimZ; // instances of Minim
recorder; // instance for audio recording
outXY, outZ; // AudioOutput (used to patch for additive-synth)
sumXY, sumZ; // Summer for patching filters
waveX, waveY, waveZ; // Oscil for each XYZ oscillators
tableX, tableY, tableZ; // XYWavetable, applied to oscillator
shapeX, shapeY, shapeZ; // float[] used for wavetables
fonts; // list of Hershey fonts

minimR, minimBG; // instances of Minim
waveR, waveG, waveB; // Oscil for each RGB oscillators
tableR, tableG, tableB; // XYWavetable, applied to oscillator
shapeR, shapeG, shapeB; // float[] used for wavetables

Extras

Contributing

Found a bug, missing feature, and/or created a project with XYscope? Let me know!
Create an issue on GitHub.

License

This project is licensed under the LGPL License - see LICENSE.md for details.

Shoutouts