Last active
May 4, 2023 19:52
-
-
Save 0x0f0f0f/71740f86f506e9528bf1e9c946636c1f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// hydra-abbreviations | |
// diminutives for all the functions | |
// by ritchse | |
const getHydra = function () { | |
const whereami = window.choo?.state?.hydra | |
? "editor" | |
: window.atom?.packages | |
? "atom" | |
: "idk"; | |
console.log(whereami); | |
switch (whereami) { | |
case "editor": | |
return choo.state.hydra.hydra; | |
case "atom": | |
return global.atom.packages.loadedPackages["atom-hydra"] | |
.mainModule.main.hydra; | |
case "idk": | |
let _h = undefined; | |
_h = window._hydra?.regl ? window._hydra : _h; | |
_h = window.hydra?.regl ? window.hydra : _h; | |
_h = window.h?.regl ? window.h : _h; | |
_h = window.H?.regl ? window.H : _h; | |
_h = window.hy?.regl ? window.hy : _h; | |
return _h; | |
} | |
}; | |
const loadExtensions = function () { | |
window._hydra = getHydra(); | |
window._hydraScope = _hydra.sandbox.makeGlobal ? window : _hydra.synth; | |
{ | |
const gS = _hydraScope.osc().constructor.prototype; | |
function wrapColorCombine(name, color, combine) { | |
gS[name] = function (arg, ...args) { | |
isArgument = | |
typeof arg == "number" || | |
typeof arg == "function" || | |
typeof arg == "string" || | |
arg.constructor.name == "Array"; | |
return isArgument | |
? this[color](arg) | |
: args.length | |
? this[combine](arg, ...args) | |
: this[combine](arg); | |
}; | |
} | |
{ | |
const noInputs = ( | |
"abs,sign,fract," + | |
"sin,cos,tan,asin,acos,atan," + | |
"exp,log,exp2,log2,sqrt,inversesqrt" | |
).split(","); | |
noInputs.forEach((name) => { | |
_hydra.synth.setFunction({ | |
name: "_" + name, | |
type: "color", | |
inputs: [], | |
glsl: `return ${name}(_c0);`, | |
}); | |
gS[name] = gS["_" + name]; | |
}); | |
} | |
{ | |
const singleArgument = "mod,min,max,step".split(","); | |
singleArgument.forEach((name) => { | |
_hydra.synth.setFunction({ | |
name: "_" + name, | |
type: "combine", | |
inputs: [], | |
glsl: `return ${name}(_c0,_c1);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "_" + name + "_single", | |
type: "color", | |
inputs: [{ name: "_in", type: "float", default: 1 }], | |
glsl: `return ${name}(_c0,_in);`, | |
}); | |
wrapColorCombine(name, "_" + name + "_single", "_" + name); | |
}); | |
} | |
_hydra.synth.setFunction({ | |
name: "_div", | |
type: "combine", | |
inputs: [], | |
glsl: `return _c0 / _c1;`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "_div_single", | |
type: "color", | |
inputs: [{ name: "_in", type: "float", default: 1 }], | |
glsl: `return _c0 / _in;`, | |
}); | |
wrapColorCombine("div", "_div_single", "_div"); | |
_hydra.synth.setFunction({ | |
name: "_add", | |
type: "combine", | |
inputs: [{ type: "float", name: "amount", default: 1 }], | |
glsl: `return _c0 + (_c1*amount);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "_add_single", | |
type: "color", | |
inputs: [{ name: "_in", type: "float", default: 1 }], | |
glsl: `return _c0 + _in;`, | |
}); | |
wrapColorCombine("add", "_add_single", "_add"); | |
_hydra.synth.setFunction({ | |
name: "_sub", | |
type: "combine", | |
inputs: [{ type: "float", name: "amount", default: 1 }], | |
glsl: `return _c0 - (_c1*amount);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "_sub_single", | |
type: "color", | |
inputs: [{ name: "_in", type: "float", default: 1 }], | |
glsl: `return _c0 - _in;`, | |
}); | |
wrapColorCombine("sub", "_sub_single", "_sub"); | |
_hydra.synth.setFunction({ | |
name: "_mult", | |
type: "combine", | |
inputs: [{ type: "float", name: "amount", default: 1 }], | |
glsl: `return _c0*(1.0-amount)+(_c0*_c1)*amount;`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "_mult_single", | |
type: "color", | |
inputs: [{ name: "_in", type: "float", default: 1 }], | |
glsl: `return _c0 * _in;`, | |
}); | |
wrapColorCombine("mult", "_mult_single", "_mult"); | |
gS.amp = gS._mult_single; | |
gS.amplitude = gS.amp; | |
gS.offset = gS._add_single; | |
gS.off = gS.offset; | |
_hydra.synth.setFunction({ | |
name: "bipolar", | |
type: "color", | |
inputs: [{ name: "amp", type: "float", default: 1 }], | |
glsl: `return ((_c0 * 2.0) - 1.0) * amp;`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "unipolar", | |
type: "color", | |
inputs: [{ name: "amp", type: "float", default: 1 }], | |
glsl: `return ((_c0 + 1.0) * 0.5) * amp;`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "range", | |
type: "color", | |
inputs: [ | |
{ name: "_min", type: "float", default: 0 }, | |
{ name: "_max", type: "float", default: 1 }, | |
], | |
glsl: `return _c0 * (_max - _min) + _min;`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "birange", | |
type: "color", | |
inputs: [ | |
{ name: "_min", type: "float", default: 0 }, | |
{ name: "_max", type: "float", default: 1 }, | |
], | |
glsl: `return ((_c0 + 1.0) * 0.5) * (_max - _min) + _min;`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "_clamp", | |
type: "color", | |
inputs: [ | |
{ name: "_min", type: "float", default: 0 }, | |
{ name: "_max", type: "float", default: 1 }, | |
], | |
glsl: `return clamp(_c0, _min, _max);`, | |
}); | |
gS.clamp = gS._clamp; | |
_hydra.synth.setFunction({ | |
name: "x", | |
type: "src", | |
inputs: [ | |
{ name: "mult", type: "float", default: 1 }, | |
], | |
glsl: `return vec4(vec3(_st.x*mult),1.0);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "y", | |
type: "src", | |
inputs: [ | |
{ name: "mult", type: "float", default: 1 }, | |
], | |
glsl: `return vec4(vec3(_st.y*mult),1.0);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "length", | |
type: "src", | |
inputs: [ | |
{ name: "mult", type: "float", default: 1 }, | |
], | |
glsl: `return vec4(vec3(length(_st*mult)),1.0);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "distance", | |
type: "src", | |
inputs: [ | |
{ name: "px", type: "float", default: 0 }, | |
{ name: "py", type: "float", default: 0 }, | |
], | |
glsl: `return vec4(vec3(length(_st,vec2(px,py))),1.0);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "xCenter", | |
type: "src", | |
inputs: [ | |
{ name: "mult", type: "float", default: 1 }, | |
], | |
glsl: `return vec4(vec3((0.5-_st.x)*mult),1.0);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "yCenter", | |
type: "src", | |
inputs: [ | |
{ name: "mult", type: "float", default: 1 }, | |
], | |
glsl: `return vec4(vec3((0.5-_st.y)*mult),1.0);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "lengthCenter", | |
type: "src", | |
inputs: [ | |
{ name: "mult", type: "float", default: 1 }, | |
], | |
glsl: `return vec4(vec3(length((vec2(0.5)-_st)*mult)),1.0);`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "distanceCenter", | |
type: "src", | |
inputs: [ | |
{ name: "px", type: "float", default: 0 }, | |
{ name: "py", type: "float", default: 0 }, | |
], | |
glsl: `return vec4(vec3(length(vec2(0.5)-_st,vec2(0.5)-vec2(px,py))),1.0);`, | |
}); | |
} | |
window.hydraArrays = {}; | |
hydraArrays.newOperator = function (self, f) { | |
return function (arr) { | |
for (let i = 0; i < self.length; i++) { | |
if (Array.isArray(arr)) { | |
self[i] = arr[i] ? f(self[i], arr[i]) : self[i]; | |
} else { | |
self[i] = f(self[i], arr); | |
} | |
} | |
return self; | |
}; | |
}; | |
hydraArrays.newWrapOperator = function (self, f) { | |
return function (arr) { | |
for (let i = 0; i < self.length; i++) { | |
if (Array.isArray(arr)) { | |
self[i] = f(self[i], arr[i % arr.length]); | |
} else { | |
self[i] = f(self[i], arr); | |
} | |
} | |
return self; | |
}; | |
}; | |
// operators | |
{ | |
const operators = { | |
add: (x, y) => x + y, | |
sub: (x, y) => x - y, | |
div: (x, y) => x / y, | |
mult: (x, y) => x * y, | |
mod: (x, y) => x % y, | |
}; | |
Object.entries(operators).forEach(function ([op, f]) { | |
Array.prototype[op] = function (arr) { | |
return hydraArrays.newOperator(this, f)(arr); | |
}; | |
Array.prototype[op + "Wrap"] = function (arr) { | |
return hydraArrays.newWrapOperator(this, f)(arr); | |
}; | |
}); | |
} | |
// methods | |
Array.prototype.shuffle = function () { | |
return this.sort(() => Math.random() - 0.5); | |
}; | |
Array.prototype.zfill = function (l, z = 0) { | |
const _l = this.length; | |
for (let i = 0; i < l - _l; i++) this.push(z); | |
return this; | |
}; | |
Array.prototype.rotate = function (n) { | |
const len = this.length; | |
this.push(...this.splice(0, ((-n % len) + len) % len)); | |
return this; | |
}; | |
Array.prototype.rot = Array.prototype.rotate; | |
// generators | |
Array.random = function (l = 10, min = 0, max = 1) { | |
return Array.from( | |
{ length: l }, | |
() => Math.random() * (max - min) + min | |
); | |
}; | |
Array.range = function (start, end, step = 1) { | |
if (step === 0) return []; | |
const length = Math.ceil((end - start) / step); | |
return Array.from({ length }, (_, i) => start + i * step).filter( | |
(n) => n <= end | |
); | |
}; | |
Array.run = function (end = 10, step = 1) { | |
return Array.range(0, end, step); | |
}; | |
{ | |
const blendingFuncs = { | |
add2: "min(base+blend,1.0)", | |
average: "(base+blend)/2.0", | |
colorBurn: "(blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0)", | |
colorDodge: "(blend==1.0)?blend:min(base/(1.0-blend),1.0)", | |
darken: "min(blend,base)", | |
difference: "abs(base-blend)", | |
divide: "min(base/(blend+0.00000001),1.0)", | |
exclusion: "base+blend-2.0*base*blend", | |
glow: "(base==1.0)?base:min(blend*blend/(1.0-base),1.0)", | |
hardLight: | |
"blend<0.5?(2.0*blend*base):(1.0-2.0*(1.0-blend)*(1.0-base))", | |
hardMix: | |
"(((blend<0.5)?((blend==0.0)?(blend):max((1.0-((1.0-base)/(2.0*blend))),0.0)):(((2.0*(blend-0.5))==1.0)?(2.0*(blend-0.5)):min(base/(1.0-(2.0*(blend-0.5))),1.0)))<0.5)?0.0:1.0", | |
lighten: "max(blend,base)", | |
linearBurn: "max(base+blend-1.0,0.0)", | |
linearDodge: "min(base+blend,1.0)", | |
linearLight: | |
"blend<0.5?(max(base+(2.0*blend)-1.0,0.0)):min(base+(2.0*(blend-0.5)),1.0)", | |
multiply: "base*blend", | |
negation: "1.0-abs(1.0-base-blend)", | |
overlay: "base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend))", | |
phoenix: "min(base,blend)-max(base,blend)+vec3(1.0)", | |
pinLight: "(blend<0.5)?min(base,2.0*blend):max(base,2.0*(blend-0.5))", | |
reflect: "(blend==1.0)?blend:min(base*base/(1.0-blend),1.0)", | |
screen: "1.0-((1.0-base)*(1.0-blend))", | |
softLight: | |
"(blend<0.5)?(2.0*base*blend+base*base*(1.0-2.0*blend)):(sqrt(base)*(2.0*blend-1.0)+2.0*base*(1.0-blend))", | |
subtract: "max(base+blend-1.0,0.0)", | |
vividLight: | |
"(blend<0.5)?((blend==0.0)?(blend):max((1.0-((1.0-base)/(2.0*blend))),0.0)):(((2.0*(blend-0.5))==1.0)?(2.0*(blend-0.5)):min(base/(1.0-(2.0*(blend-0.5))),1.0))", | |
}; | |
Object.entries(blendingFuncs).forEach(([mode, def]) => { | |
let glsl = | |
"vec3 rgb;\n\n" + | |
//"_c0 = clamp(_c0, 0.0, 1.0);" + | |
//"_c1 = clamp(_c1, 0.0, 1.0);" + | |
["r", "g", "b"].reduce((acc, elem) => { | |
return ( | |
acc + | |
(`rgb.${elem} = ` + | |
def | |
.replaceAll("base", "_c0." + elem) | |
.replaceAll("blend", "_c1." + elem) + | |
";\n\n") | |
); | |
}, "") + | |
"_c1.a *= amount;" + | |
"vec4 blended = vec4(mix(_c0.rgb, rgb, _c1.a), 1.0);" + | |
"vec4 over = _c1+(_c0*(1.0-_c1.a));" + | |
"return mix(blended, over, 1.0-_c0.a);"; | |
_hydra.synth.setFunction({ | |
name: mode, | |
type: "combine", | |
inputs: [{ name: "amount", type: "float", default: 1 }], | |
glsl, | |
}); | |
}); | |
} | |
_hydra.synth.setFunction({ | |
name: "layer", | |
type: "combine", | |
inputs: [ | |
{ | |
type: "float", | |
name: "amount", | |
default: 1, | |
}, | |
], | |
glsl: ` | |
_c0.a = clamp(_c0.a, 0.0, 1.0); | |
_c1.a = clamp(_c1.a, 0.0, 1.0); | |
return _c1+(_c0*(1.0-_c1.a)); | |
`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "mask", | |
type: "combine", | |
inputs: [], | |
glsl: ` | |
float a = _luminance(_c1.rgb); | |
return vec4(_c0.rgb*a, _c0.a*a); | |
`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "luma", | |
type: "color", | |
inputs: [ | |
{ | |
type: "float", | |
name: "threshold", | |
default: 0.5, | |
}, | |
{ | |
type: "float", | |
name: "tolerance", | |
default: 0.1, | |
}, | |
], | |
glsl: ` | |
float a = smoothstep(threshold-(tolerance+0.0000001), threshold+(tolerance+0.0000001), _luminance(_c0.rgb*_c0.a)); | |
return vec4(_c0.rgb*a, a); | |
`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "clamp", | |
type: "color", | |
inputs: [], | |
glsl: ` | |
return clamp(_c0,0.0,1.0); | |
`, | |
}); | |
_hydra.synth.setFunction({ | |
name: "premultiply", | |
type: "color", | |
inputs: [], | |
glsl: ` | |
_c0 = clamp(_c0,0.0,1.0); | |
return vec4(_c0.rgb*_c0.a,_c0.a); | |
`, | |
}); | |
{ | |
const gS = _hydraScope.osc().constructor.prototype; | |
gS.pm = gS.premultiply; | |
gS.negate = gS.negation; | |
} | |
_hydraScope.canvas = window._hydra.canvas; | |
_hydraScope.canvas.setLinear = function () { | |
this.style.imageRendering = "auto"; | |
}; | |
_hydraScope.canvas.setNearest = function () { | |
this.style.imageRendering = "pixelated"; | |
}; | |
_hydraScope.canvas.setFullscreen = function (full = true) { | |
const set = full ? "100%" : ""; | |
this.style.width = set; | |
this.style.height = set; | |
}; | |
_hydraScope.canvas.setAlign = function (align = "right") { | |
this.parentElement.style["text-align"] = align; | |
}; | |
_hydraScope.canvas.setRelativeSize = function (ratio) { | |
this.style.width = "" + width * ratio + "px"; | |
this.style.height = "" + height * ratio + "px"; | |
}; | |
window.gS = _hydraScope.osc().constructor.prototype; | |
{ | |
const hcs = {}; | |
hcs.colorspaces = [ | |
{ | |
name: "rgb", | |
elems: ["r", "g", "b", "a"], | |
to: "r = _r; g = _g; b = _b; a = _a;", | |
from: "_r = r; _g = g; _b = b; _a = a;", | |
}, | |
{ | |
name: "cmyk", | |
elems: ["c", "m", "y", "k"], | |
to: ` | |
k = 1.0-max(_r,max(_g,_b)); | |
c = (1.0-_r-k) / (1.0-k); | |
m = (1.0-_g-k) / (1.0-k); | |
y = (1.0-_b-k) / (1.0-k); | |
`, | |
from: ` | |
_r = (1.0-c)*(1.0-k); | |
_g = (1.0-m)*(1.0-k); | |
_b = (1.0-y)*(1.0-k); | |
`, | |
}, | |
{ | |
name: "hsv", | |
elems: ["h", "s", "v"], | |
to: ` | |
vec3 _hsv = _rgbToHsv(vec3(_r,_g,_b)); | |
h = _hsv.x; s = _hsv.y; v = _hsv.z; | |
`, | |
from: ` | |
vec3 _rgb = _hsvToRgb(vec3(h,s,v)); | |
_r = _rgb.r; _g = _rgb.g; _b = _rgb.b; | |
`, | |
}, | |
{ | |
name: "hsl", | |
elems: ["h", "s", "l"], | |
to: ` | |
vec3 _hsv = _rgbToHsv(vec3(_r,_g,_b)); | |
h = _hsv.x; | |
l = _hsv.z*(1.0-(_hsv.y*0.5)); | |
s = (_hsv.z-l)/(min(l,1.0-l)); | |
s *= step(-l,-0.000001)*step(l,0.999999); | |
`, | |
from: ` | |
_hsv.x = h; | |
_hsv.z = l + (s*min(l,1.0-l)); | |
_hsv.y = 2.0*(1.0-(l/_hsv.z))*step(-_hsv.z,-0.000001); | |
vec3 _rgb = _hsvToRgb(_hsv); | |
_r = _rgb.r; _g = _rgb.g; _b = _rgb.b; | |
`, | |
}, | |
{ | |
name: "yuv", | |
elems: ["y", "u", "v"], | |
to: ` | |
mat3 rgb2yuv = mat3(0.2126, 0.7152, 0.0722, -0.09991, -0.33609, 0.43600, 0.615, -0.5586, -0.05639); | |
vec3 _yuv = vec3(_r,_g,_b) * rgb2yuv; | |
y = _yuv.x; u = _yuv.y; v = _yuv.z; | |
`, | |
from: ` | |
mat3 yuv2rgb = mat3(1.0, 0.0, 1.28033, 1.0, -0.21482, -0.38059, 1.0, 2.12798, 0.0); | |
vec3 _rgb = vec3(y,u,v) * yuv2rgb; | |
_r = _rgb.r; _g = _rgb.g; _b = _rgb.b; | |
`, | |
}, | |
{ | |
name: "yiq", | |
elems: ["y", "i", "q"], | |
to: ` | |
mat3 rgb2yiq = mat3(0.299, 0.587, 0.114, 0.5959, -0.2746, -0.3213, 0.2115, -0.5227, 0.3112); | |
vec3 _yiq = vec3(_r,_g,_b) * rgb2yiq; | |
y = _yiq.x; i = _yiq.y; q = _yiq.z; | |
`, | |
from: ` | |
mat3 yiq2rgb = mat3(1.0, 0.956, 0.619, 1.0, -0.272, -0.647, 1.0, -1.106, 1.703); | |
vec3 _rgb = vec3(y,i,q) * yiq2rgb; | |
_r = _rgb.r; _g = _rgb.g; _b = _rgb.b; | |
`, | |
}, | |
]; | |
// utils | |
hcs.generateInputAssignment = function (elems, format) { | |
return format | |
? elems | |
.map((el) => { | |
let assign = format; | |
if ( | |
"yuvalpha,yiqalpha".includes(elems.join("")) && | |
"uviq".includes(el) && | |
assign.includes("1.0 - #el") | |
) { | |
assign = assign.replaceAll("1.0 - #el", "0.0 - #el"); | |
} // TODO: find a better solution for this patch on inversion | |
return assign | |
.replaceAll("#el", el) | |
.replaceAll("#in", "in_" + el); | |
}) | |
.join("") | |
: ""; | |
}; | |
hcs.generateDirectAssignment = function (elems, tofrom) { | |
const rgba = ["_r", "_g", "_b", "_a"]; | |
return tofrom == "to" | |
? elems.map((el, i) => rgba[i] + " = " + el + ";").join("") | |
: elems.map((el, i) => el + " = " + rgba[i] + ";").join(""); | |
}; | |
hcs.generateDeclarations = function (elems, type = "float") { | |
return elems.map((el) => type + " " + el + ";\n").join(""); | |
}; | |
// functions that use all elems | |
hcs.generateFunction = function ({ | |
colorspace, | |
sufix, | |
type, | |
assignmentFormat, | |
inputDefault, | |
alphaDefault = 1, | |
tofrom, | |
}) { | |
const name = colorspace.name + (sufix ? "_" + sufix : ""); | |
const hasColorInput = ["color", "combine"].includes(type); | |
const isRgb = "rgba".includes(colorspace.name); | |
const elems = | |
isRgb || tofrom | |
? colorspace.elems | |
: colorspace.elems.concat("alpha"); | |
const inputs = assignmentFormat | |
? elems.map((el) => ({ | |
type: "float", | |
name: "in_" + el, | |
default: inputDefault, | |
})) | |
: []; | |
inputs.length ? (inputs.at(-1).default = alphaDefault) : null; | |
const rgbaDeclarations = hcs.generateDeclarations([ | |
"_r", | |
"_g", | |
"_b", | |
"_a", | |
]); | |
const rgbaAssignments = hasColorInput | |
? "_r = _c0.r; _g = _c0.g; _b = _c0.b; _a = _c0.a;" | |
: ""; | |
const elemDeclarations = | |
hcs.generateDeclarations(elems) + | |
(isRgb || tofrom ? "" : "alpha = _a;"); | |
const to = hasColorInput || tofrom == "to" ? colorspace.to : ""; | |
const elemAssignments = assignmentFormat | |
? hcs.generateInputAssignment(elems, assignmentFormat) | |
: hcs.generateDirectAssignment(elems, tofrom); | |
const from = !tofrom || tofrom == "from" ? colorspace.from : ""; | |
const returner = | |
(isRgb || tofrom ? "" : "_a = alpha;") + | |
"return vec4(_r,_g,_b,_a);"; | |
const glsl = | |
rgbaDeclarations + | |
rgbaAssignments + | |
elemDeclarations + | |
to + | |
elemAssignments + | |
from + | |
returner; | |
return { name: name, type: type, inputs: inputs, glsl: glsl }; | |
}; | |
hcs.generateColorFunction = (cs) => | |
hcs.generateFunction({ | |
colorspace: cs, | |
sufix: "color", | |
type: "color", | |
assignmentFormat: "#el *= #in;", | |
inputDefault: 1, | |
alphaDefault: 1, | |
}); | |
hcs.generateOffsetFunction = (cs) => | |
hcs.generateFunction({ | |
colorspace: cs, | |
sufix: "offset", | |
type: "color", | |
assignmentFormat: "#el += #in;", | |
inputDefault: 0, | |
alphaDefault: 0, | |
}); | |
hcs.generateSolidFunction = (cs) => | |
hcs.generateFunction({ | |
colorspace: cs, | |
sufix: "solid", | |
type: "src", | |
assignmentFormat: "#el = #in;", | |
inputDefault: 0, | |
alphaDefault: 1, | |
}); | |
hcs.generateToFunction = (cs) => | |
hcs.generateFunction({ | |
colorspace: cs, | |
sufix: "to", | |
type: "color", | |
assignmentFormat: undefined, | |
inputDefault: 0, | |
alphaDefault: 1, | |
tofrom: "to", | |
}); | |
hcs.generateFromFunction = (cs) => | |
hcs.generateFunction({ | |
colorspace: cs, | |
sufix: "from", | |
type: "color", | |
assignmentFormat: undefined, | |
inputDefault: 0, | |
alphaDefault: 1, | |
tofrom: "from", | |
}); | |
hcs.generateInvertFunction = (cs) => | |
hcs.generateFunction({ | |
colorspace: cs, | |
sufix: "invert", | |
type: "color", | |
assignmentFormat: "#el = mix(#el, 1.0 - #el, #in);", | |
inputDefault: 1, | |
alphaDefault: 0, | |
}); | |
// elem functions | |
hcs.generateElementFunction = function (colorspace, elem) { | |
const name = colorspace.name + "_" + elem; | |
const type = "color"; | |
const rgbaDeclarations = hcs.generateDeclarations([ | |
"_r", | |
"_g", | |
"_b", | |
"_a", | |
]); | |
const rgbaAssignments = | |
"_r = _c0.r; _g = _c0.g; _b = _c0.b; _a = _c0.a;"; | |
const elemDeclarations = hcs.generateDeclarations(colorspace.elems); | |
const to = colorspace.to; | |
const returner = "return vec4(vec3(" + elem + "),1.0);"; | |
const glsl = | |
rgbaDeclarations + | |
rgbaAssignments + | |
elemDeclarations + | |
to + | |
returner; | |
return { name: name, type: type, inputs: [], glsl: glsl }; | |
}; | |
hcs.generateElementFunctions = (cs) => | |
cs.elems.map((elem) => hcs.generateElementFunction(cs, elem)); | |
hcs.generateSetElementFunction = function ({ | |
colorspace, | |
elem, | |
sufix, | |
assignmentFormat = "#el = #in;", | |
inputDefault = 1, | |
}) { | |
const name = colorspace.name + "_" + elem + "_" + sufix; | |
const type = "color"; | |
const inputs = [ | |
{ type: "float", name: "in_" + elem, default: inputDefault }, | |
]; | |
const rgbaDeclarations = hcs.generateDeclarations([ | |
"_r", | |
"_g", | |
"_b", | |
"_a", | |
]); | |
const rgbaAssignments = | |
"_r = _c0.r; _g = _c0.g; _b = _c0.b; _a = _c0.a;"; | |
const elemDeclarations = hcs.generateDeclarations(colorspace.elems); | |
const to = colorspace.to; | |
const elemAssignment = hcs.generateInputAssignment( | |
[elem], | |
assignmentFormat | |
); | |
const from = colorspace.from; | |
const returner = "return vec4(_r,_g,_b,_a);"; | |
const glsl = | |
rgbaDeclarations + | |
rgbaAssignments + | |
elemDeclarations + | |
to + | |
elemAssignment + | |
from + | |
returner; | |
return { name: name, type: type, inputs: inputs, glsl: glsl }; | |
}; | |
hcs.generateSetElementFunctions = (cs) => | |
cs.elems.map((elem) => | |
hcs.generateSetElementFunction({ | |
colorspace: cs, | |
elem, | |
sufix: "set", | |
assignmentFormat: "#el = #in;", | |
}) | |
); | |
hcs.generateOffsetElementFunctions = (cs) => | |
cs.elems.map((elem) => | |
hcs.generateSetElementFunction({ | |
colorspace: cs, | |
elem, | |
sufix: "offset", | |
assignmentFormat: "#el += #in;", | |
inputDefault: 1, | |
}) | |
); | |
hcs.generateMultElementFunctions = (cs) => | |
cs.elems.map((elem) => | |
hcs.generateSetElementFunction({ | |
colorspace: cs, | |
elem, | |
sufix: "mult", | |
assignmentFormat: "#el *= #in;", | |
inputDefault: 1, | |
}) | |
); | |
hcs.generateInvertElementFunctions = (cs) => | |
cs.elems.map((elem) => | |
hcs.generateSetElementFunction({ | |
colorspace: cs, | |
elem, | |
sufix: "invert", | |
assignmentFormat: "#el = mix(#el, 1.0 - #el, #in);", | |
inputDefault: 1, | |
}) | |
); | |
hcs.generateCombineElementFunction = function ({ | |
colorspace, | |
elem, | |
sufix, | |
assignmentFormat = "#el = _c1.r;", | |
inputDefault = 1, | |
}) { | |
const name = colorspace.name + "_" + elem + "_" + sufix; | |
const type = "combine"; | |
const inputs = [{ type: "float", name: "_amt", default: inputDefault }]; | |
const rgbaDeclarations = hcs.generateDeclarations([ | |
"_r", | |
"_g", | |
"_b", | |
"_a", | |
]); | |
const rgbaAssignments = | |
"_r = _c0.r; _g = _c0.g; _b = _c0.b; _a = _c0.a;"; | |
const elemDeclarations = hcs.generateDeclarations(colorspace.elems); | |
const to = colorspace.to; | |
const elemAssignment = hcs.generateInputAssignment( | |
[elem], | |
assignmentFormat | |
); | |
const from = colorspace.from; | |
const returner = "return vec4(_r,_g,_b,_a);"; | |
const glsl = | |
rgbaDeclarations + | |
rgbaAssignments + | |
elemDeclarations + | |
to + | |
elemAssignment + | |
from + | |
returner; | |
return { name: name, type: type, inputs: inputs, glsl: glsl }; | |
}; | |
hcs.generateSetElementFromFunctions = (cs) => | |
cs.elems.map((elem) => | |
hcs.generateCombineElementFunction({ | |
colorspace: cs, | |
elem, | |
sufix: "from", | |
assignmentFormat: "#el = mix(#el,_c1.r,_amt);", | |
inputDefault: 1, | |
}) | |
); | |
hcs.generateOffsetElementFromFunctions = (cs) => | |
cs.elems.map((elem) => | |
hcs.generateCombineElementFunction({ | |
colorspace: cs, | |
elem, | |
sufix: "offset_from", | |
assignmentFormat: "#el += _c1.r*_amt;", | |
inputDefault: 1, | |
}) | |
); | |
hcs.generateMultElementFromFunctions = (cs) => | |
cs.elems.map((elem) => | |
hcs.generateCombineElementFunction({ | |
colorspace: cs, | |
elem, | |
sufix: "mult_from", | |
assignmentFormat: "#el *= _c1.r*_amt;", | |
inputDefault: 1, | |
}) | |
); | |
// keying | |
hcs.generateKeyingElementFunction = function ({ colorspace, elem }) { | |
const name = colorspace.name + "_" + elem + "_" + "key"; | |
const type = "color"; | |
const inputs = [ | |
{ type: "float", name: "_th0", default: 0.5 }, | |
{ type: "float", name: "_t0", default: 0.05 }, | |
{ type: "float", name: "_th1", default: 0 }, | |
{ type: "float", name: "_t1", default: 0 }, | |
]; | |
const isRgb = "rgba".includes(colorspace.name); | |
const rgbaDeclarations = hcs.generateDeclarations([ | |
"_r", | |
"_g", | |
"_b", | |
"_a", | |
]); | |
const rgbaAssignments = | |
"_r = _c0.r; _g = _c0.g; _b = _c0.b; _a = _c0.a;"; | |
const elemDeclarations = hcs.generateDeclarations(colorspace.elems); | |
const to = colorspace.to; | |
const keying = ( | |
"float _key = smoothstep(_th0-(_t0+0.0000001), _th0+(_t0+0.0000001), #elem);" + | |
"_th1 = 1.0 - _th1 + 0.0000001; _key *= smoothstep(_th1-(-_t1-0.0000001), _th1+(-_t1-0.0000001), #elem);" + | |
(isRgb ? "a" : "_a") + | |
" *= _key;" | |
).replaceAll("#elem", elem); | |
const from = colorspace.from; | |
const returner = "return vec4(_r,_g,_b,_a);"; | |
const glsl = | |
rgbaDeclarations + | |
rgbaAssignments + | |
elemDeclarations + | |
to + | |
keying + | |
from + | |
returner; | |
return { name: name, type: type, inputs: inputs, glsl: glsl }; | |
}; | |
hcs.generateKeyingElementFunctions = (cs) => | |
cs.elems.map((elem) => | |
hcs.generateKeyingElementFunction({ | |
colorspace: cs, | |
elem, | |
}) | |
); | |
// updaters | |
hcs.updateFunctions = function () { | |
[] | |
.concat( | |
hcs.colorspaces.map(hcs.generateColorFunction), | |
hcs.colorspaces.map(hcs.generateOffsetFunction), | |
hcs.colorspaces.map(hcs.generateSolidFunction), | |
hcs.colorspaces.map(hcs.generateToFunction), | |
hcs.colorspaces.map(hcs.generateFromFunction), | |
hcs.colorspaces.map(hcs.generateInvertFunction), | |
hcs.colorspaces.map(hcs.generateElementFunctions), | |
hcs.colorspaces.map(hcs.generateSetElementFunctions), | |
hcs.colorspaces.map(hcs.generateOffsetElementFunctions), | |
hcs.colorspaces.map(hcs.generateMultElementFunctions), | |
hcs.colorspaces.map(hcs.generateInvertElementFunctions), | |
hcs.colorspaces.map(hcs.generateSetElementFromFunctions), | |
hcs.colorspaces.map(hcs.generateOffsetElementFromFunctions), | |
hcs.colorspaces.map(hcs.generateMultElementFromFunctions), | |
hcs.colorspaces.map(hcs.generateKeyingElementFunctions) | |
) | |
.flat(99) | |
.filter((x) => x) | |
.forEach((x) => _hydra.synth.setFunction(x)); | |
}; | |
hcs.cloneGlslSource = function (_tex) { | |
const tex = Object.assign({}, _tex); | |
Object.setPrototypeOf(tex, gS); | |
tex.transforms = Array.from(_tex.transforms); | |
return tex; | |
}; | |
hcs.updateWithFunctions = function () { | |
hcs.colorspaces.forEach((cs) => { | |
cs.elems.forEach((el) => { | |
gS[cs.name + "_" + el + "_" + "with"] = function (f) { | |
const tex = hcs.cloneGlslSource(this); | |
return this[cs.name + "_" + el + "_" + "from"]( | |
f(tex)[cs.name + "_" + el]() | |
); | |
}; | |
}); | |
}); | |
}; | |
hcs.update = function () { | |
hcs.updateFunctions(); | |
hcs.updateWithFunctions(); | |
hcs.colorspaces.forEach((cs) => { | |
let getterDefinition = | |
"Object.defineProperty(gS, '#cs', { configurable: true, get: function() {" + | |
"const func = this.#cs_color.bind(this);"; | |
getterDefinition += | |
"const props = {" + | |
"color: this.#cs_color.bind(this)," + | |
"offset: this.#cs_offset.bind(this)," + | |
"to: this.#cs_to.bind(this)," + | |
"from: this.#cs_from.bind(this)," + | |
"invert: this.#cs_invert.bind(this),"; | |
cs.elems.forEach((elem) => { | |
getterDefinition += | |
"#elem: this.#cs_#elem.bind(this)," + | |
"#elemSet: this.#cs_#elem_set.bind(this)," + | |
"#elemOffset: this.#cs_#elem_offset.bind(this)," + | |
"#elemMult: this.#cs_#elem_mult.bind(this)," + | |
"#elemInvert: this.#cs_#elem_invert.bind(this)," + | |
"#elemFrom: this.#cs_#elem_from.bind(this)," + | |
"#elemOffsetFrom: this.#cs_#elem_offset_from.bind(this)," + | |
"#elemMultFrom: this.#cs_#elem_mult_from.bind(this)," + | |
"#elemKey: this.#cs_#elem_key.bind(this)," + | |
"#elemWith: this.#cs_#elem_with.bind(this),"; | |
getterDefinition = getterDefinition.replaceAll("#elem", elem); | |
}); | |
getterDefinition += "};"; | |
getterDefinition += | |
"Object.assign(func,props);" + "return func; }, });"; | |
getterDefinition += "_hydraScope.#cs = _hydraScope.#cs_solid;"; | |
getterDefinition = getterDefinition.replaceAll("#cs", cs.name); | |
window.eval(getterDefinition); | |
}); | |
}; | |
hcs.update(); | |
window.hydraColorspaces = {}; | |
hydraColorspaces.colorspaces = hcs.colorspaces; | |
hydraColorspaces.update = hcs.update.bind(hcs); | |
} | |
{ | |
async function main() { | |
await _hydraScope.loadScript( | |
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js" | |
); | |
await _hydraScope.loadScript( | |
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/glsl.min.js" | |
); | |
await _hydraScope.loadScript( | |
"https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.14.7/beautify.js" | |
); | |
hljs.configure({ ignoreUnescapedHTML: true }); | |
const brPlugin = { | |
"before:highlightBlock": ({ block }) => { | |
block.innerHTML = block.innerHTML | |
.replace(/\n/g, "") | |
.replace(/<br[ /]*>/g, "\n"); | |
}, | |
"after:highlightBlock": ({ result }) => { | |
result.value = result.value.replace(/\n/g, "<br>"); | |
}, | |
}; | |
hljs.addPlugin(brPlugin); | |
const link = document.createElement("link"); | |
link.href = | |
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/base16/tomorrow-night.min.css"; | |
link.type = "text/css"; | |
link.rel = "stylesheet"; | |
document.getElementsByTagName("head")[0].appendChild(link); | |
{ | |
function cleanCode(code) { | |
return js_beautify(code).replace(/\n{2,}/gm, "\n\n") + "\n\n"; | |
} | |
function logHighlightedCode(glslSource, output) { | |
let passes = glslSource.glsl(); | |
let code = passes[0].frag; | |
document.getElementById("hydra-debug")?.remove(); | |
const pre = document.createElement("pre"); | |
pre.className = "hljs"; | |
pre.style.position = "sticky"; | |
pre.style.height = "96%"; | |
pre.style.padding = "4%"; | |
pre.style.overflow = "scroll"; | |
const codeElement = document.createElement("code"); | |
codeElement.className = "language-glsl"; | |
pre.appendChild(codeElement); | |
codeElement.innerText = cleanCode(code) | |
.replace(/&/g, "&") | |
.replace(/</g, "<") | |
.replace(/>/g, ">") | |
.replace(/"/g, """) | |
.replace(/'/g, "'"); | |
hljs.highlightElement(pre, { | |
language: "glsl", | |
ignoreIllegals: true, | |
}); | |
const wrapper = document.createElement("div"); | |
wrapper.id = "hydra-debug"; | |
wrapper.style.zIndex = 999; | |
wrapper.style.overflow = "hidden"; | |
wrapper.style.position = "fixed"; | |
wrapper.style.width = "40%"; | |
wrapper.style.height = "90%"; | |
wrapper.style.left = "58%"; | |
wrapper.style.top = "5%"; | |
wrapper.style.fontSize = "14px"; | |
const close = document.createElement("button"); | |
close.innerText = "x"; | |
close.style.position = "absolute"; | |
close.style.right = "0px"; | |
close.style.top = "2%"; | |
close.style.fontSize = "20px"; | |
close.style.backgroundColor = "white"; | |
close.style.color = "black"; | |
close.style.border = "none"; | |
close.onclick = () => { | |
document.getElementById("hydra-debug")?.remove(); | |
}; | |
wrapper.appendChild(pre); | |
wrapper.appendChild(close); | |
if (output) { | |
pre.contentEditable = "true"; | |
const run = document.createElement("button"); | |
run.innerText = ">"; | |
run.style.position = "absolute"; | |
run.style.right = "30px"; | |
run.style.top = "2%"; | |
run.style.fontSize = "20px"; | |
run.style.backgroundColor = "white"; | |
run.style.color = "black"; | |
run.style.border = "none"; | |
run.onclick = () => { | |
codeElement.innerText = cleanCode(pre.innerText) | |
pre.innerHTML = ""; | |
pre.appendChild(codeElement); | |
hljs.highlightElement(pre, { | |
language: "glsl", | |
ignoreIllegals: true, | |
}); | |
passes[0].frag = pre.innerText | |
.replace("&", "&") | |
.replace("<", "<") | |
.replace(">", ">") | |
.replace(""", '"') | |
.replace("'", "'"); | |
output.render(passes); | |
}; | |
wrapper.appendChild(run); | |
} | |
document.body.append(wrapper); | |
} | |
const gS = _hydraScope.osc().constructor.prototype; | |
gS.debug = function (output) { | |
logHighlightedCode(this, output); | |
return output ? this.out(output) : this; | |
}; | |
} | |
} | |
main(); | |
} | |
[ | |
{ | |
name: "mirrorX", | |
type: "coord", | |
inputs: [ | |
{ | |
type: "float", | |
name: "pos", | |
default: 0, | |
}, | |
{ | |
type: "float", | |
name: "coverage", | |
default: 1, | |
}, | |
], | |
glsl: `_st.x = (0.0-abs(fract(_st.x/coverage)-(1.0-0.5-pos))+0.5-pos)*coverage; return _st;`, | |
}, | |
{ | |
name: "mirrorY", | |
type: "coord", | |
inputs: [ | |
{ | |
type: "float", | |
name: "pos", | |
default: 0, | |
}, | |
{ | |
type: "float", | |
name: "coverage", | |
default: 1, | |
}, | |
], | |
glsl: `_st.y = (0.0-abs(fract(_st.y/coverage)-(1.0-0.5-pos))+0.5-pos)*coverage; return _st;`, | |
}, | |
{ | |
name: "mirrorX2", | |
type: "coord", | |
inputs: [ | |
{ | |
type: "float", | |
name: "pos", | |
default: 0, | |
}, | |
{ | |
type: "float", | |
name: "coverage", | |
default: 1, | |
}, | |
], | |
glsl: `_st.x = (abs(fract(_st.x/coverage)-(1.0-0.5-pos))+0.5-pos)*coverage; return _st;`, | |
}, | |
{ | |
name: "mirrorY2", | |
type: "coord", | |
inputs: [ | |
{ | |
type: "float", | |
name: "pos", | |
default: 0, | |
}, | |
{ | |
type: "float", | |
name: "coverage", | |
default: 1, | |
}, | |
], | |
glsl: `_st.y = (0.0-abs(fract(_st.y/coverage)-(1.0-0.5-pos))+0.5-pos)*coverage; return _st;`, | |
}, | |
{ | |
name: "mirrorWrap", | |
type: "coord", | |
inputs: [], | |
glsl: `return -abs(fract(_st/2.0)*2.0-1.0)+1.0;`, | |
}, | |
{ | |
name: "inversion", | |
type: "coord", | |
inputs: [], | |
glsl: `_st /= dot(_st,_st); return _st;`, | |
}, | |
].forEach((x) => _hydra.synth.setFunction(x)); | |
// https://stackoverflow.com/questions/48234696/how-to-put-a-gif-with-canvas | |
window.GIF = function () { | |
// **NOT** for commercial use. | |
var timerID; // timer handle for set time out usage | |
var st; // holds the stream object when loading. | |
var interlaceOffsets = [0, 4, 2, 1]; // used in de-interlacing. | |
var interlaceSteps = [8, 8, 4, 2]; | |
var interlacedBufSize; // this holds a buffer to de interlace. Created on the first frame and when size changed | |
var deinterlaceBuf; | |
var pixelBufSize; // this holds a buffer for pixels. Created on the first frame and when size changed | |
var pixelBuf; | |
const GIF_FILE = { | |
// gif file data headers | |
GCExt: 0xf9, | |
COMMENT: 0xfe, | |
APPExt: 0xff, | |
UNKNOWN: 0x01, // not sure what this is but need to skip it in parser | |
IMAGE: 0x2c, | |
EOF: 59, // This is entered as decimal | |
EXT: 0x21, | |
}; | |
// simple buffered stream used to read from the file | |
var Stream = function (data) { | |
this.data = new Uint8ClampedArray(data); | |
this.pos = 0; | |
var len = this.data.length; | |
this.getString = function (count) { | |
// returns a string from current pos of len count | |
var s = ""; | |
while (count--) { | |
s += String.fromCharCode(this.data[this.pos++]); | |
} | |
return s; | |
}; | |
this.readSubBlocks = function () { | |
// reads a set of blocks as a string | |
var size, | |
count, | |
data = ""; | |
do { | |
count = size = this.data[this.pos++]; | |
while (count--) { | |
data += String.fromCharCode(this.data[this.pos++]); | |
} | |
} while (size !== 0 && this.pos < len); | |
return data; | |
}; | |
this.readSubBlocksB = function () { | |
// reads a set of blocks as binary | |
var size, | |
count, | |
data = []; | |
do { | |
count = size = this.data[this.pos++]; | |
while (count--) { | |
data.push(this.data[this.pos++]); | |
} | |
} while (size !== 0 && this.pos < len); | |
return data; | |
}; | |
}; | |
// LZW decoder uncompressed each frames pixels | |
// this needs to be optimised. | |
// minSize is the min dictionary as powers of two | |
// size and data is the compressed pixels | |
function lzwDecode(minSize, data) { | |
var i, pixelPos, pos, clear, eod, size, done, dic, code, last, d, len; | |
pos = pixelPos = 0; | |
dic = []; | |
clear = 1 << minSize; | |
eod = clear + 1; | |
size = minSize + 1; | |
done = false; | |
while (!done) { | |
// JavaScript optimisers like a clear exit though I never use 'done' apart from fooling the optimiser | |
last = code; | |
code = 0; | |
for (i = 0; i < size; i++) { | |
if (data[pos >> 3] & (1 << (pos & 7))) { | |
code |= 1 << i; | |
} | |
pos++; | |
} | |
if (code === clear) { | |
// clear and reset the dictionary | |
dic = []; | |
size = minSize + 1; | |
for (i = 0; i < clear; i++) { | |
dic[i] = [i]; | |
} | |
dic[clear] = []; | |
dic[eod] = null; | |
} else { | |
if (code === eod) { | |
done = true; | |
return; | |
} | |
if (code >= dic.length) { | |
dic.push(dic[last].concat(dic[last][0])); | |
} else if (last !== clear) { | |
dic.push(dic[last].concat(dic[code][0])); | |
} | |
d = dic[code]; | |
len = d.length; | |
for (i = 0; i < len; i++) { | |
pixelBuf[pixelPos++] = d[i]; | |
} | |
if (dic.length === 1 << size && size < 12) { | |
size++; | |
} | |
} | |
} | |
} | |
function parseColourTable(count) { | |
// get a colour table of length count Each entry is 3 bytes, for RGB. | |
var colours = []; | |
for (var i = 0; i < count; i++) { | |
colours.push([ | |
st.data[st.pos++], | |
st.data[st.pos++], | |
st.data[st.pos++], | |
]); | |
} | |
return colours; | |
} | |
function parse() { | |
// read the header. This is the starting point of the decode and async calls parseBlock | |
var bitField; | |
st.pos += 6; | |
gif.width = st.data[st.pos++] + (st.data[st.pos++] << 8); | |
gif.height = st.data[st.pos++] + (st.data[st.pos++] << 8); | |
bitField = st.data[st.pos++]; | |
gif.colorRes = (bitField & 0b1110000) >> 4; | |
gif.globalColourCount = 1 << ((bitField & 0b111) + 1); | |
gif.bgColourIndex = st.data[st.pos++]; | |
st.pos++; // ignoring pixel aspect ratio. if not 0, aspectRatio = (pixelAspectRatio + 15) / 64 | |
if (bitField & 0b10000000) { | |
gif.globalColourTable = parseColourTable(gif.globalColourCount); | |
} // global colour flag | |
setTimeout(parseBlock, 0); | |
} | |
function parseAppExt() { | |
// get application specific data. Netscape added iterations and terminator. Ignoring that | |
st.pos += 1; | |
if ("NETSCAPE" === st.getString(8)) { | |
st.pos += 8; | |
} // ignoring this data. iterations (word) and terminator (byte) | |
else { | |
st.pos += 3; // 3 bytes of string usually "2.0" when identifier is NETSCAPE | |
st.readSubBlocks(); // unknown app extension | |
} | |
} | |
function parseGCExt() { | |
// get GC data | |
var bitField; | |
st.pos++; | |
bitField = st.data[st.pos++]; | |
gif.disposalMethod = (bitField & 0b11100) >> 2; | |
gif.transparencyGiven = bitField & 0b1 ? true : false; // ignoring bit two that is marked as userInput??? | |
gif.delayTime = st.data[st.pos++] + (st.data[st.pos++] << 8); | |
gif.transparencyIndex = st.data[st.pos++]; | |
st.pos++; | |
} | |
function parseImg() { | |
// decodes image data to create the indexed pixel image | |
var deinterlace, frame, bitField; | |
deinterlace = function (width) { | |
// de interlace pixel data if needed | |
var lines, fromLine, pass, toline; | |
lines = pixelBufSize / width; | |
fromLine = 0; | |
if (interlacedBufSize !== pixelBufSize) { | |
// create the buffer if size changed or undefined. | |
deinterlaceBuf = new Uint8Array(pixelBufSize); | |
interlacedBufSize = pixelBufSize; | |
} | |
for (pass = 0; pass < 4; pass++) { | |
for ( | |
toLine = interlaceOffsets[pass]; | |
toLine < lines; | |
toLine += interlaceSteps[pass] | |
) { | |
deinterlaceBuf.set( | |
pixelBuf.subarray(fromLine, fromLine + width), | |
toLine * width | |
); | |
fromLine += width; | |
} | |
} | |
}; | |
frame = {}; | |
gif.frames.push(frame); | |
frame.disposalMethod = gif.disposalMethod; | |
frame.time = gif.length; | |
frame.delay = gif.delayTime * 10; | |
gif.length += frame.delay; | |
if (gif.transparencyGiven) { | |
frame.transparencyIndex = gif.transparencyIndex; | |
} else { | |
frame.transparencyIndex = undefined; | |
} | |
frame.leftPos = st.data[st.pos++] + (st.data[st.pos++] << 8); | |
frame.topPos = st.data[st.pos++] + (st.data[st.pos++] << 8); | |
frame.width = st.data[st.pos++] + (st.data[st.pos++] << 8); | |
frame.height = st.data[st.pos++] + (st.data[st.pos++] << 8); | |
bitField = st.data[st.pos++]; | |
frame.localColourTableFlag = bitField & 0b10000000 ? true : false; | |
if (frame.localColourTableFlag) { | |
frame.localColourTable = parseColourTable( | |
1 << ((bitField & 0b111) + 1) | |
); | |
} | |
if (pixelBufSize !== frame.width * frame.height) { | |
// create a pixel buffer if not yet created or if current frame size is different from previous | |
pixelBuf = new Uint8Array(frame.width * frame.height); | |
pixelBufSize = frame.width * frame.height; | |
} | |
lzwDecode(st.data[st.pos++], st.readSubBlocksB()); // decode the pixels | |
if (bitField & 0b1000000) { | |
// de interlace if needed | |
frame.interlaced = true; | |
deinterlace(frame.width); | |
} else { | |
frame.interlaced = false; | |
} | |
processFrame(frame); // convert to canvas image | |
} | |
function processFrame(frame) { | |
// creates a RGBA canvas image from the indexed pixel data. | |
var ct, cData, dat, pixCount, ind, useT, i, pixel, pDat, col, frame, ti; | |
frame.image = document.createElement("canvas"); | |
frame.image.width = gif.width; | |
frame.image.height = gif.height; | |
frame.image.ctx = frame.image.getContext("2d"); | |
ct = frame.localColourTableFlag | |
? frame.localColourTable | |
: gif.globalColourTable; | |
if (gif.lastFrame === null) { | |
gif.lastFrame = frame; | |
} | |
useT = | |
gif.lastFrame.disposalMethod === 2 || | |
gif.lastFrame.disposalMethod === 3 | |
? true | |
: false; | |
if (!useT) { | |
frame.image.ctx.drawImage( | |
gif.lastFrame.image, | |
0, | |
0, | |
gif.width, | |
gif.height | |
); | |
} | |
cData = frame.image.ctx.getImageData( | |
frame.leftPos, | |
frame.topPos, | |
frame.width, | |
frame.height | |
); | |
ti = frame.transparencyIndex; | |
dat = cData.data; | |
if (frame.interlaced) { | |
pDat = deinterlaceBuf; | |
} else { | |
pDat = pixelBuf; | |
} | |
pixCount = pDat.length; | |
ind = 0; | |
for (i = 0; i < pixCount; i++) { | |
pixel = pDat[i]; | |
col = ct[pixel]; | |
if (ti !== pixel) { | |
dat[ind++] = col[0]; | |
dat[ind++] = col[1]; | |
dat[ind++] = col[2]; | |
dat[ind++] = 255; // Opaque. | |
} else if (useT) { | |
dat[ind + 3] = 0; // Transparent. | |
ind += 4; | |
} else { | |
ind += 4; | |
} | |
} | |
frame.image.ctx.putImageData(cData, frame.leftPos, frame.topPos); | |
gif.lastFrame = frame; | |
if (!gif.waitTillDone && typeof gif.onload === "function") { | |
doOnloadEvent(); | |
} // if !waitTillDone the call onload now after first frame is loaded | |
} | |
// **NOT** for commercial use. | |
function finnished() { | |
// called when the load has completed | |
gif.loading = false; | |
gif.frameCount = gif.frames.length; | |
gif.lastFrame = null; | |
st = undefined; | |
gif.complete = true; | |
gif.disposalMethod = undefined; | |
gif.transparencyGiven = undefined; | |
gif.delayTime = undefined; | |
gif.transparencyIndex = undefined; | |
gif.waitTillDone = undefined; | |
pixelBuf = undefined; // dereference pixel buffer | |
deinterlaceBuf = undefined; // dereference interlace buff (may or may not be used); | |
pixelBufSize = undefined; | |
deinterlaceBuf = undefined; | |
gif.currentFrame = 0; | |
if (gif.frames.length > 0) { | |
gif.image = gif.frames[0].image; | |
} | |
doOnloadEvent(); | |
if (typeof gif.onloadall === "function") { | |
gif.onloadall.bind(gif)({ type: "loadall", path: [gif] }); | |
} | |
if (gif.playOnLoad) { | |
gif.play(); | |
} | |
} | |
function canceled() { | |
// called if the load has been cancelled | |
finnished(); | |
if (typeof gif.cancelCallback === "function") { | |
gif.cancelCallback.bind(gif)({ type: "canceled", path: [gif] }); | |
} | |
} | |
function parseExt() { | |
// parse extended blocks | |
const blockID = st.data[st.pos++]; | |
if (blockID === GIF_FILE.GCExt) { | |
parseGCExt(); | |
} else if (blockID === GIF_FILE.COMMENT) { | |
gif.comment += st.readSubBlocks(); | |
} else if (blockID === GIF_FILE.APPExt) { | |
parseAppExt(); | |
} else { | |
if (blockID === GIF_FILE.UNKNOWN) { | |
st.pos += 13; | |
} // skip unknow block | |
st.readSubBlocks(); | |
} | |
} | |
function parseBlock() { | |
// parsing the blocks | |
if (gif.cancel !== undefined && gif.cancel === true) { | |
canceled(); | |
return; | |
} | |
const blockId = st.data[st.pos++]; | |
if (blockId === GIF_FILE.IMAGE) { | |
// image block | |
parseImg(); | |
if (gif.firstFrameOnly) { | |
finnished(); | |
return; | |
} | |
} else if (blockId === GIF_FILE.EOF) { | |
finnished(); | |
return; | |
} else { | |
parseExt(); | |
} | |
if (typeof gif.onprogress === "function") { | |
gif.onprogress({ | |
bytesRead: st.pos, | |
totalBytes: st.data.length, | |
frame: gif.frames.length, | |
}); | |
} | |
setTimeout(parseBlock, 0); // parsing frame async so processes can get some time in. | |
} | |
function cancelLoad(callback) { | |
// cancels the loading. This will cancel the load before the next frame is decoded | |
if (gif.complete) { | |
return false; | |
} | |
gif.cancelCallback = callback; | |
gif.cancel = true; | |
return true; | |
} | |
function error(type) { | |
if (typeof gif.onerror === "function") { | |
gif.onerror.bind(this)({ type: type, path: [this] }); | |
} | |
gif.onload = gif.onerror = undefined; | |
gif.loading = false; | |
} | |
function doOnloadEvent() { | |
// fire onload event if set | |
gif.currentFrame = 0; | |
gif.nextFrameAt = gif.lastFrameAt = new Date().valueOf(); // just sets the time now | |
if (typeof gif.onload === "function") { | |
gif.onload.bind(gif)({ type: "load", path: [gif] }); | |
} | |
gif.onerror = gif.onload = undefined; | |
} | |
function dataLoaded(data) { | |
// Data loaded create stream and parse | |
st = new Stream(data); | |
parse(); | |
} | |
function loadGif(filename) { | |
// starts the load | |
var ajax = new XMLHttpRequest(); | |
ajax.responseType = "arraybuffer"; | |
ajax.onload = function (e) { | |
if (e.target.status === 404) { | |
error("File not found"); | |
} else if (e.target.status >= 200 && e.target.status < 300) { | |
dataLoaded(ajax.response); | |
} else { | |
error("Loading error : " + e.target.status); | |
} | |
}; | |
ajax.open("GET", filename, true); | |
ajax.send(); | |
ajax.onerror = function (e) { | |
error("File error"); | |
}; | |
this.src = filename; | |
this.loading = true; | |
} | |
function play() { | |
// starts play if paused | |
if (!gif.playing) { | |
gif.paused = false; | |
gif.playing = true; | |
playing(); | |
} | |
} | |
function pause() { | |
// stops play | |
gif.paused = true; | |
gif.playing = false; | |
clearTimeout(timerID); | |
} | |
function togglePlay() { | |
if (gif.paused || !gif.playing) { | |
gif.play(); | |
} else { | |
gif.pause(); | |
} | |
} | |
function seekFrame(frame) { | |
// seeks to frame number. | |
clearTimeout(timerID); | |
gif.currentFrame = frame % gif.frames.length; | |
if (gif.playing) { | |
playing(); | |
} else { | |
gif.image = gif.frames[gif.currentFrame].image; | |
} | |
} | |
function seek(time) { | |
// time in Seconds // seek to frame that would be displayed at time | |
clearTimeout(timerID); | |
if (time < 0) { | |
time = 0; | |
} | |
time *= 1000; // in ms | |
time %= gif.length; | |
var frame = 0; | |
while ( | |
time > gif.frames[frame].time + gif.frames[frame].delay && | |
frame < gif.frames.length | |
) { | |
frame += 1; | |
} | |
gif.currentFrame = frame; | |
if (gif.playing) { | |
playing(); | |
} else { | |
gif.image = gif.frames[gif.currentFrame].image; | |
} | |
} | |
function playing() { | |
var frame; | |
if (gif.playSpeed === 0) { | |
gif.pause(); | |
return; | |
} else { | |
if (gif.playSpeed < 0) { | |
gif.currentFrame -= 1; | |
if (gif.currentFrame < 0) { | |
gif.currentFrame = gif.frames.length - 1; | |
} | |
frame = gif.currentFrame; | |
frame -= 1; | |
if (frame < 0) { | |
frame = gif.frames.length - 1; | |
} | |
} else { | |
gif.currentFrame += 1; | |
gif.currentFrame %= gif.frames.length; | |
} | |
gif.image = gif.frames[gif.currentFrame].image; | |
timerID = setTimeout(playing, gif.delay / gif.playSpeed); | |
} | |
} | |
var gif = { | |
// the gif image object | |
onload: null, // fire on load. Use waitTillDone = true to have load fire at end or false to fire on first frame | |
onerror: null, // fires on error | |
onprogress: null, // fires a load progress event | |
onloadall: null, // event fires when all frames have loaded and gif is ready | |
paused: false, // true if paused | |
playing: false, // true if playing | |
waitTillDone: true, // If true onload will fire when all frames loaded, if false, onload will fire when first frame has loaded | |
loading: false, // true if still loading | |
firstFrameOnly: false, // if true only load the first frame | |
width: null, // width in pixels | |
height: null, // height in pixels | |
frames: [], // array of frames | |
comment: "", // comments if found in file. Note I remember that some gifs have comments per frame if so this will be all comment concatenated | |
length: 0, // gif length in ms (1/1000 second) | |
currentFrame: 0, // current frame. | |
frameCount: 0, // number of frames | |
playSpeed: 1, // play speed 1 normal, 2 twice 0.5 half, -1 reverse etc... | |
delay: 100, | |
lastFrame: null, // temp hold last frame loaded so you can display the gif as it loads | |
image: null, // the current image at the currentFrame | |
playOnLoad: true, // if true starts playback when loaded | |
// functions | |
load: loadGif, // call this to load a file | |
cancel: cancelLoad, // call to stop loading | |
play: play, // call to start play | |
pause: pause, // call to pause | |
seek: seek, // call to seek to time | |
seekFrame: seekFrame, // call to seek to frame | |
togglePlay: togglePlay, // call to toggle play and pause state | |
}; | |
return gif; | |
}; | |
{ | |
if (!window._updateChain) { | |
window.update = window.update || ((dt) => { }); | |
window._updateChain = [() => window["update"]()]; | |
_hydra.sandbox.userProps = ["speed", "bpm", "fps"]; | |
_hydra.synth.update = (dt) => { | |
for (func of window._updateChain) { | |
func(dt); | |
} | |
}; | |
} | |
} | |
{ | |
const hS = _hydra.s[0].constructor.prototype; | |
_hydra.s.forEach((source) => { | |
source._updateChainIndex = window._updateChain.push(() => { }); | |
}); | |
hS.initGif = function (url, delay, params) { | |
const self = this; | |
self.gifCanvas = document.createElement("canvas"); | |
self.gifCtx = self.gifCanvas.getContext("2d"); | |
self.gif = new GIF(); | |
self.gif.load(url); | |
self.gif.onloadall = () => { | |
self.gif.delay = delay ? delay : self.gif.frames[0].delay; | |
self.gifCanvas.width = self.gif.width; | |
self.gifCanvas.height = self.gif.height; | |
window._updateChain[self._updateChainIndex] = () => { | |
try { | |
self.gifCtx.clearRect( | |
0, | |
0, | |
self.gifCanvas.width, | |
self.gifCanvas.height | |
); | |
self.gifCtx.drawImage(self.gif.image, 0, 0); | |
} catch (e) { | |
//console.log(e); | |
} | |
}; | |
self.init({ src: self.gifCanvas }, params); | |
}; | |
}; | |
} | |
//hydra-gsls | |
//code glsl on the fly inside Hydra code | |
//by RITCHSE | |
//docs: https://github.com/ritchse/hydra-extensions/blob/main/doc/hydra-glsl.md | |
{ | |
const gS = _hydraScope.osc().constructor.prototype; | |
const _glslExtension = { | |
inputArray: () => | |
new Array(10) | |
.fill("i") | |
.map((x, i) => ({ name: x + i, type: "float", default: 1 })), | |
nodeCount: 0, | |
checkCode: function (code) { | |
code = code.trim(); | |
let lines = code.split(";"); | |
let ll = lines.length - 1; | |
if (!lines[ll]) { | |
lines.pop(); | |
ll--; | |
} | |
lines[ll] = lines[ll].trim(); | |
lines[ll] = | |
"\n" + | |
(lines[ll].substring(0, 6) != "return" | |
? "return " + lines[ll] | |
: lines[ll]) + | |
";"; | |
code = lines.join(";"); | |
return code; | |
}, | |
getObjAndArgs: function (type, args) { | |
let inputArray = false; | |
if (args[0] instanceof Array && args[0][0].constructor === String) { | |
inputArray = this.inputArray().map((x, i) => { | |
x.name = args[i] ? args[i][0] : x.name; | |
return x; | |
}); | |
inputArray = inputArray.slice(0, args.length); | |
args = args.map((x) => x[1]); | |
} | |
let obj = { | |
name: "_glsl_ext_NODE_" + this.nodeCount, | |
type: type, | |
inputs: inputArray || this.inputArray(), | |
}; | |
this.nodeCount++; | |
return [obj, args]; | |
}, | |
glslSource: function (code, ...args) { | |
let prefix = [ | |
!code.includes("vec2 uv") ? "vec2 uv = _st;\n" : "", | |
!code.includes("vec2 st") ? "vec2 st = _st;\n" : "", | |
!code.includes("vec2 xy") ? "vec2 xy = _st;\n" : "", | |
].join(""); | |
let data = this.getObjAndArgs("src", args); | |
let obj = data[0]; | |
args = data[1]; | |
obj.glsl = prefix + this.checkCode(code); | |
_hydra.synth.setFunction(obj); | |
return globalThis[obj.name](...args); | |
}, | |
glslColor: function (self, code, ...args) { | |
let prefix = [ | |
!code.includes("vec4 c0") ? "vec4 c0 = _c0;\n" : "", | |
!code.includes("vec4 color") ? "vec4 color = _c0;\n" : "", | |
].join(""); | |
let data = this.getObjAndArgs("color", args); | |
let obj = data[0]; | |
args = data[1]; | |
obj.glsl = prefix + this.checkCode(code); | |
_hydra.synth.setFunction(obj); | |
return gS[obj.name].bind(self)(...args); | |
}, | |
glslHsv: function (self, code, ...args) { | |
code = "vec3 hsv = _rgbToHsv(c0.rgb);\n" + code; | |
code = code + "\nreturn vec4(_hsvToRgb(hsv),c0.a);"; | |
return this.glslColor(self, code, ...args); | |
}, | |
glslCoord: function (self, code, ...args) { | |
let prefix = [ | |
!code.includes("vec2 uv") ? "vec2 uv = _st;\n" : "", | |
!code.includes("vec2 st") ? "vec2 st = _st;\n" : "", | |
!code.includes("vec2 xy") ? "vec2 xy = _st;\n" : "", | |
].join(""); | |
let data = this.getObjAndArgs("coord", args); | |
let obj = data[0]; | |
args = data[1]; | |
obj.glsl = prefix + this.checkCode(code); | |
_hydra.synth.setFunction(obj); | |
return gS[obj.name].bind(self)(...args); | |
}, | |
glslCombine: function (self, code, texture, ...args) { | |
let prefix = [ | |
!code.includes("vec4 c0") ? "vec4 c0 = _c0;\n" : "", | |
!code.includes("vec4 color0") ? "vec4 color0 = _c0;\n" : "", | |
!code.includes("vec4 c1") ? "vec4 c1 = _c1;\n" : "", | |
!code.includes("vec4 color1") ? "vec4 color1 = _c1;\n" : "", | |
].join(""); | |
let data = this.getObjAndArgs("combine", args); | |
let obj = data[0]; | |
args = data[1]; | |
args.unshift(texture); | |
obj.glsl = prefix + this.checkCode(code); | |
_hydra.synth.setFunction(obj); | |
return gS[obj.name].bind(self)(...args); | |
}, | |
glslCombineCoord: function (self, code, texture, ...args) { | |
let prefix = [ | |
!code.includes("vec4 c0") ? "vec4 c0 = _c0;\n" : "", | |
!code.includes("vec4 color") ? "vec4 color = _c0;\n" : "", | |
!code.includes("vec2 uv") ? "vec2 uv = _st;\n" : "", | |
!code.includes("vec2 st") ? "vec2 st = _st;\n" : "", | |
!code.includes("vec2 xy") ? "vec2 xy = _st;\n" : "", | |
].join(""); | |
let data = this.getObjAndArgs("combineCoord", args); | |
let obj = data[0]; | |
args = data[1]; | |
args.unshift(texture); | |
obj.glsl = prefix + this.checkCode(code); | |
_hydra.synth.setFunction(obj); | |
return gS[obj.name].bind(self)(...args); | |
}, | |
}; | |
_hydraScope.glsl = _glslExtension.glslSource.bind(_glslExtension); | |
gS.glslColor = function (code, ...args) { | |
return _glslExtension.glslColor(this, code, ...args); | |
}; | |
gS.glslHsv = function (code, ...args) { | |
return _glslExtension.glslHsv(this, code, ...args); | |
}; | |
gS.glslCoord = function (code, ...args) { | |
return _glslExtension.glslCoord(this, code, ...args); | |
}; | |
gS.glslCombine = function (code, texture, ...args) { | |
return _glslExtension.glslCombine(this, code, texture, ...args); | |
}; | |
gS.glslBlend = gS.glslCombine; | |
gS.glslCombineCoord = function (code, texture, ...args) { | |
return _glslExtension.glslCombineCoord(this, code, texture, ...args); | |
}; | |
gS.glslModulate = gS.glslCombineCoord; | |
} | |
function Mouse(canvas) { | |
this.x = 0; | |
this.y = 0; | |
this.ax = 0; | |
this.ay = 0; | |
this.cx = 0; | |
this.cy = 0; | |
this.rx = 0; | |
this.ry = 0; | |
this.crx = 0; | |
this.cry = 0; | |
this.posx = 0; | |
this.posy = 0; | |
this.cposx = 0; | |
this.cposy = 0; | |
this.canvas = canvas; | |
let self = this; | |
this.handlePointerMove = function (ev) { | |
const bound = self.canvas.getBoundingClientRect(); | |
self.x = ev.clientX; | |
self.y = ev.clientY; | |
self.ax = ev.pageX; | |
self.ay = ev.pageY; | |
self.cx = self.ax - bound.left; | |
self.cy = self.ay - bound.top; | |
self.rx = self.x / window.innerWidth; | |
self.ry = self.y / window.innerHeight; | |
self.crx = self.cx / bound.width; | |
self.cry = self.cy / bound.height; | |
self.posx = -self.x / window.innerWidth + 0.5; | |
self.posy = -self.y / window.innerHeight + 0.5; | |
self.cposx = -self.cx / bound.width + 0.5; | |
self.cposy = -self.cy / bound.height + 0.5; | |
}; | |
if (window.mouse.handlePointerMove) | |
window.removeEventListener( | |
"pointermove", | |
window.mouse.handlePointerMove | |
); | |
window.addEventListener("pointermove", this.handlePointerMove); | |
} | |
_hydraScope.mouse = new Mouse(window._hydra.canvas); | |
{ | |
const oP = _hydra.o[0].constructor.prototype; | |
oP.fboSettings = Array(2).fill({ | |
mag: "nearest", | |
min: "nearest", | |
width: width, | |
height: height, | |
format: "rgba", | |
}); | |
oP.setFbos = function (fbo0, fbo1) { | |
var colors = fbo1 ? [fbo0, fbo1] : [fbo0, fbo0]; | |
this.fboSettings = colors.map((x, i) => { | |
return { | |
...this.fboSettings[i], | |
width: width, | |
height: height, | |
...x, | |
}; | |
}); | |
this.fbos = this.fboSettings.map((x) => | |
this.regl.framebuffer({ | |
color: this.regl.texture(x), | |
depthStencil: false, | |
}) | |
); | |
}; | |
oP.setNearest = function () { | |
this.setFbos({ mag: "nearest", min: "nearest" }); | |
}; | |
oP.setLinear = function () { | |
this.setFbos({ mag: "linear", min: "linear" }); | |
}; | |
oP.setRepeat = function () { | |
this.setFbos({ wrapS: "repeat", wrapT: "repeat" }); | |
}; | |
oP.setClamp = function () { | |
this.setFbos({ wrapS: "clamp", wrapT: "clamp" }); | |
}; | |
oP.setMirror = function () { | |
this.setFbos({ wrapS: "mirror", wrapT: "mirror" }); | |
}; | |
_hydraScope.oS = { outputs: window._hydra.o }; | |
_hydraScope.oS.setNearest = function () { | |
this.outputs.forEach((x) => x.setNearest()); | |
}; | |
_hydraScope.oS.setLinear = function () { | |
this.outputs.forEach((x) => x.setLinear()); | |
}; | |
_hydraScope.oS.setRepeat = function () { | |
this.outputs.forEach((x) => x.setWrap()); | |
}; | |
_hydraScope.oS.setClamp = function () { | |
this.outputs.forEach((x) => x.setClamp()); | |
}; | |
_hydraScope.oS.setMirror = function () { | |
this.outputs.forEach((x) => x.setMirror()); | |
}; | |
_hydraScope.oS.setFbos = function (_x, y) { | |
this.outputs.forEach((x) => x.setFbos(_x, y)); | |
}; | |
} | |
{ | |
const video = document.createElement("video"); video.autoplay = true; | |
const canvas = _hydra.canvas; | |
const stream = canvas.captureStream(); | |
video.srcObject = stream; | |
window.hydraPip = () => video.requestPictureInPicture(); | |
window.hydraPictureInPicture = window.hydraPip; | |
} | |
//hydra-pixels | |
//read pixels from each hydra output | |
//by RITCHSE | |
{ | |
const oP = _hydra.o[0].constructor.prototype; | |
const regl = _hydra.o[0].regl; | |
regl.attributes.preserveDrawingBuffer = true; | |
oP.read = function (x = 0, y = 0, w = 1, h = 1) { | |
return regl.read({ | |
framebuffer: this.fbos[this.pingPongIndex], | |
x: x, | |
y: y, | |
width: w, | |
height: h, | |
}); | |
}; | |
oP.readAll = function () { | |
const fbo = this.fbos[this.pingPongIndex]; | |
return regl.read({ | |
framebuffer: fbo, | |
x: 0, | |
y: 0, | |
width: fbo.width, | |
height: fbo.height, | |
}); | |
}; | |
} | |
{ | |
const canvas = _hydra.canvas; | |
const scope = (_hydraScope.srcMask = function (tex) { | |
return _hydraScope.src(tex).mask(shape(4, 1, 0)); | |
}); | |
_hydraScope.srcAbs = function (tex) { | |
if (!tex.hasOwnProperty("src")) return src(tex); | |
const w = () => tex.src?.width || tex.src?.videoWidth || 0; | |
const h = () => tex.src?.height || tex.src?.videoHeight || 0; | |
return _hydraScope.src(tex).scale( | |
1, | |
() => w() / canvas.clientWidth, | |
() => h() / canvas.clientHeight | |
); | |
}; | |
_hydraScope.srcAbsMask = function (tex) { | |
if (!tex.hasOwnProperty("src")) return src(tex); | |
const w = () => tex.src?.width || tex.src?.videoWidth || 0; | |
const h = () => tex.src?.height || tex.src?.videoHeight || 0; | |
return _hydraScope.srcMask(tex).scale( | |
1, | |
() => w() / canvas.clientWidth, | |
() => h() / canvas.clientHeight | |
); | |
}; | |
_hydraScope.srcRel = function (tex) { | |
if (!tex.hasOwnProperty("src")) return src(tex); | |
const w = () => | |
tex.src?.width | |
? tex.src?.width / tex.src?.height | |
: tex.src?.videoWidth | |
? tex.src?.videoWidth / tex.src?.videoHeight | |
: 0; | |
const h = () => | |
tex.src?.height | |
? tex.src?.height / tex.src?.width | |
: tex.src?.videoHeight | |
? tex.src?.videoHeight / tex.src?.videoWidth | |
: 0; | |
const cw = () => canvas.clientWidth / canvas.clientHeight; | |
const ch = () => canvas.clientHeight / canvas.clientWidth; | |
return _hydraScope.src(tex).scale( | |
1, | |
() => { | |
const _cw = cw(); | |
const _w = w(); | |
return _cw > _w ? _w / _cw : 1; | |
}, | |
() => { | |
const _ch = ch(); | |
const _h = h(); | |
return _ch > _h ? _h / _ch : 1; | |
} | |
); | |
}; | |
_hydraScope.srcRelMask = function (tex) { | |
if (!tex.hasOwnProperty("src")) return src(tex); | |
const w = () => | |
tex.src?.width | |
? tex.src?.width / tex.src?.height | |
: tex.src?.videoWidth | |
? tex.src?.videoWidth / tex.src?.videoHeight | |
: 0; | |
const h = () => | |
tex.src?.height | |
? tex.src?.height / tex.src?.width | |
: tex.src?.videoHeight | |
? tex.src?.videoHeight / tex.src?.videoWidth | |
: 0; | |
const cw = () => canvas.clientWidth / canvas.clientHeight; | |
const ch = () => canvas.clientHeight / canvas.clientWidth; | |
return _hydraScope.srcMask(tex).scale( | |
1, | |
() => { | |
const _cw = cw(); | |
const _w = w(); | |
return _cw > _w ? _w / _cw : 1; | |
}, | |
() => { | |
const _ch = ch(); | |
const _h = h(); | |
return _ch > _h ? _h / _ch : 1; | |
} | |
); | |
}; | |
} | |
{ | |
const gS = _hydraScope.osc().constructor.prototype; | |
// https://stackoverflow.com/questions/34127294/ | |
function getSwizzles(coords) { | |
function combinations(input, length, curstr) { | |
if (curstr.length == length) return [curstr]; | |
var ret = []; | |
for (var i = 0; i < input.length; i++) { | |
ret.push.apply( | |
ret, | |
combinations(input, length, curstr + input[i]) | |
); | |
} | |
return ret; | |
} | |
let ret = combinations(coords, coords.length, ""); | |
ret.splice(ret.indexOf("input"), 1); | |
return ret; | |
} | |
const threeComponents = [].concat(getSwizzles("rgb"), getSwizzles("xyz")); | |
const fourComponents = [].concat(getSwizzles("rgba"), getSwizzles("xyzw")); | |
function definePropertyFromMethod(method, newName = "") { | |
newName = newName ? newName : method; | |
Object.defineProperty(gS, newName, { | |
configurable: true, | |
get() { | |
return this[method].bind(this)(); | |
}, | |
}); | |
} | |
threeComponents.forEach((swizzle) => { | |
const name = "_" + swizzle; | |
_hydra.synth.setFunction({ | |
name, | |
type: "color", | |
inputs: [], | |
glsl: `return _c0.${swizzle}a;`, | |
}); | |
definePropertyFromMethod(name, swizzle); | |
}); | |
fourComponents.forEach((swizzle) => { | |
const name = "_" + swizzle; | |
_hydra.synth.setFunction({ | |
name, | |
type: "color", | |
inputs: [], | |
glsl: `return _c0.${swizzle};`, | |
}); | |
definePropertyFromMethod(name, swizzle); | |
}); | |
Array.from("rgbaxyzw").forEach((elem) => { | |
const name = "_swizzle_" + elem; | |
_hydra.synth.setFunction({ | |
name, | |
type: "color", | |
inputs: [], | |
glsl: `return _c0.${elem + elem + elem}a;`, | |
}); | |
definePropertyFromMethod(name, elem); | |
}); | |
} | |
_hydraScope.srcRelMask = function (tex) { | |
if (!tex.hasOwnProperty("src")) return src(tex); | |
const w = () => | |
tex.src?.width | |
? tex.src?.width / tex.src?.height | |
: tex.src?.videoWidth | |
? tex.src?.videoWidth / tex.src?.videoHeight | |
: 0; | |
const h = () => | |
tex.src?.height | |
? tex.src?.height / tex.src?.width | |
: tex.src?.videoHeight | |
? tex.src?.videoHeight / tex.src?.videoWidth | |
: 0; | |
const cw = () => _hydra.canvas.clientWidth / _hydra.canvas.clientHeight; | |
const ch = () => _hydra.canvas.clientHeight / _hydra.canvas.clientWidth; | |
return _hydraScope | |
.src(tex) | |
.mask(shape(4, 1, 0)) | |
.scale( | |
1, | |
() => { | |
const _cw = cw(); | |
const _w = w(); | |
return _cw > _w ? _w / _cw : 1; | |
}, | |
() => { | |
const _ch = ch(); | |
const _h = h(); | |
return _ch > _h ? _h / _ch : 1; | |
} | |
); | |
}; | |
{ | |
const Source = _hydra.s[0].constructor; | |
window.hydraText = { | |
font: "sans-serif", | |
fontStyle: "normal", | |
fontSize: "auto", | |
textAlign: "center", | |
fillStyle: "white", | |
strokeStyle: "white", | |
lineWidth: "2%", | |
lineJoin: "miter", | |
canvasResize: 2, | |
interpolation: "linear" | |
}; | |
function createSource() { | |
const s = new Source({ | |
regl: _hydra.regl, | |
pb: _hydra.pb, | |
width: _hydra.width, | |
height: _hydra.height, | |
}); | |
return s; | |
} | |
function isPercentage(property) { | |
return String(property).endsWith("%"); | |
} | |
function getPercentage(property) { | |
return Number(property.substring(0, property.length - 1)) / 100; | |
} | |
const _text = function (str, _config, fill = true, stroke = false, fillAfter = false) { | |
const s = createSource(); | |
const canvas = document.createElement("canvas"); | |
const ctx = canvas.getContext("2d"); | |
const lines = str.split("\n"); | |
const longestLine = lines.reduce((a, b) => a.length > b.length ? a : b); | |
if (typeof _config == "string") _config = { font: _config }; | |
const config = Object.assign({}, hydraText); | |
Object.assign(config, _config); | |
const font = config.font; | |
config.font = undefined; | |
const fontStyle = config.fontStyle; | |
config.fontStyle = undefined; | |
config.textBaseline = "middle"; | |
const fontWithSize = (size) => `${fontStyle} ${size} ${font}`; | |
Object.assign(ctx, config); | |
canvas.width = _hydra.width; | |
ctx.font = fontWithSize("1px"); | |
let padding = _hydra.width / 20; | |
let textWidth = _hydra.width - padding; | |
let fontSize = textWidth / ctx.measureText(longestLine).width; | |
canvas.height = fontSize * 1.4 * lines.length; | |
if (isPercentage(config.fontSize)) fontSize *= getPercentage(config.fontSize); | |
else if (config.fontSize != "auto") fontSize = Number(config.fontSize.replace(/[^0-9.,]+/, '')); | |
if (isPercentage(config.lineWidth)) config.lineWidth = fontSize * getPercentage(config.lineWidth); | |
config.fontSize = undefined; | |
fontSize *= config.canvasResize; | |
canvas.width *= config.canvasResize; | |
canvas.height *= config.canvasResize; | |
textWidth *= config.canvasResize; | |
padding *= config.canvasResize; | |
config.lineWidth *= config.canvasResize; | |
config.font = fontWithSize(String(fontSize) + "px"); | |
Object.assign(ctx, config); | |
let x = 0; | |
if (ctx.textAlign == "center") x = canvas.width / 2; | |
else if (ctx.textAlign == "left") x = padding / 2; | |
else if (ctx.textAlign == "right") x = canvas.width - padding / 2; | |
lines.forEach((line, i) => { | |
const y = (canvas.height / (lines.length + 1)) * (i + 1); | |
if (fill) ctx.fillText(line, x, y, textWidth); | |
if (stroke) ctx.strokeText(line, x, y, textWidth); | |
if (fillAfter) ctx.fillText(line, x, y, textWidth); | |
}); | |
s.init({ src: canvas }, { min: config.interpolation, mag: config.interpolation }); | |
return _hydraScope.srcRelMask(s); | |
}; | |
_hydraScope.text = function (str, config) { | |
return _text(str, config); | |
}; | |
_hydraScope.strokeText = function (str, config) { | |
return _text(str, config, false, true); | |
}; | |
_hydraScope.fillStrokeText = function (str, config) { | |
return _text(str, config, true, true, false); | |
}; | |
_hydraScope.strokeFillText = function (str, config) { | |
return _text(str, config, false, true, true); | |
}; | |
} | |
{ | |
function filterVecArgs(args) { | |
let pass = []; | |
args.forEach((arg) => { | |
if (typeof arg == "number" || typeof arg == "function") | |
pass = pass.concat(arg); | |
else if (arg.constructor.name == "Array") { | |
if (arg._vec) pass = pass.concat(arg); | |
else pass = pass.concat([arg]); | |
} | |
}); | |
return pass; | |
} | |
window.vec4 = function (...args) { | |
args = filterVecArgs(args); | |
if (args.length == 1) { | |
const arg = args[0]; | |
return solid(arg, arg, arg, arg); | |
} else if (args.length == 4) { | |
return _hydraScope.solid(...args); | |
} else { | |
throw new Error("vec4 should receive 4 elements"); | |
} | |
}; | |
window.vec3 = function (...args) { | |
args = filterVecArgs(args); | |
if (args.length == 1) { | |
const arg = args[0]; | |
let ret = Array.from({ length: 3 }, () => arg); | |
ret._vec = true; | |
return ret; | |
} else if (args.length == 3) { | |
args._vec = true; | |
return args; | |
} else { | |
throw new Error("vec3 should receive 3 elements"); | |
} | |
}; | |
window.vec2 = function (...args) { | |
args = filterVecArgs(args); | |
if (args.length == 1) { | |
const arg = args[0]; | |
let ret = Array.from({ length: 2 }, () => arg); | |
ret._vec = true; | |
return ret; | |
} else if (args.length == 2) { | |
args._vec = true; | |
return args; | |
} else { | |
throw new Error("vec2 should receive 2 elements"); | |
} | |
}; | |
} | |
// prereq | |
//reset fbos. make sure they use clamp | |
//code taken from hydra-outputs.js | |
{ | |
if (!_hydraScope.oS) { | |
const oP = _hydra.o[0].constructor.prototype; | |
oP.fboSettings = Array(2).fill({ | |
mag: "nearest", | |
min: "nearest", | |
width: width, | |
height: height, | |
format: "rgba", | |
}); | |
oP.setFbos = function (fbo0, fbo1) { | |
var colors = fbo1 ? [fbo0, fbo1] : [fbo0, fbo0]; | |
this.fboSettings = colors.map((x, i) => { | |
return { | |
...this.fboSettings[i], | |
width: width, | |
height: height, | |
...x, | |
}; | |
}); | |
this.fbos = this.fboSettings.map((x) => | |
this.regl.framebuffer({ | |
color: this.regl.texture(x), | |
depthStencil: false, | |
}) | |
); | |
}; | |
oP.setClamp = function () { | |
this.setFbos({ wrapS: "clamp", wrapT: "clamp" }); | |
}; | |
_hydraScope.oS = { outputs: window._hydra.o }; | |
_hydraScope.oS.setClamp = function () { | |
this.outputs.forEach((x) => x.setClamp()); | |
}; | |
} | |
_hydraScope.oS.setClamp(); | |
} | |
// set all coord functions to no-wrap | |
[ | |
{ | |
name: "scroll", | |
type: "coord", | |
inputs: [ | |
{ | |
type: "float", | |
name: "scrollX", | |
default: 0.5, | |
}, | |
{ | |
type: "float", | |
name: "scrollY", | |
default: 0.5, | |
}, | |
{ | |
type: "float", | |
name: "speedX", | |
default: 0, | |
}, | |
{ | |
type: "float", | |
name: "speedY", | |
default: 0, | |
}, | |
], | |
glsl: `_st.x += scrollX + time*speedX; _st.y += scrollY + time*speedY; return _st;`, | |
}, | |
{ | |
name: "scrollX", | |
type: "coord", | |
inputs: [ | |
{ | |
type: "float", | |
name: "scrollX", | |
default: 0.5, | |
}, | |
{ | |
type: "float", | |
name: "speed", | |
default: 0, | |
}, | |
], | |
glsl: `_st.x += scrollX + time*speed; return _st;`, | |
}, | |
{ | |
name: "modulateScrollX", | |
type: "combineCoord", | |
inputs: [ | |
{ | |
type: "float", | |
name: "scrollX", | |
default: 0.5, | |
}, | |
{ | |
type: "float", | |
name: "speed", | |
default: 0, | |
}, | |
], | |
glsl: `_st.x += _c0.r*scrollX + time*speed; return _st;`, | |
}, | |
{ | |
name: "scrollY", | |
type: "coord", | |
inputs: [ | |
{ | |
type: "float", | |
name: "scrollY", | |
default: 0.5, | |
}, | |
{ | |
type: "float", | |
name: "speed", | |
default: 0, | |
}, | |
], | |
glsl: `_st.y += scrollY + time*speed; return _st;`, | |
}, | |
{ | |
name: "modulateScrollY", | |
type: "combineCoord", | |
inputs: [ | |
{ | |
type: "float", | |
name: "scrollY", | |
default: 0.5, | |
}, | |
{ | |
type: "float", | |
name: "speed", | |
default: 0, | |
}, | |
], | |
glsl: ` _st.y += _c0.r*scrollY + time*speed; return _st;`, | |
}, | |
].forEach((x) => _hydra.synth.setFunction(x)); | |
// hydraWrap | |
window.hydraWrap = {}; | |
hydraWrap.defaultList = [ | |
{ | |
name: "prev", | |
type: "src", | |
inputs: [], | |
glsl: ` vec4 c0 = texture2D(prevBuffer, wrap(_st)); | |
//c0 *= step(abs(_st.x-0.5),0.5); | |
//c0 *= step(abs(_st.t-0.5),0.5); | |
return c0;`, | |
}, | |
{ | |
name: "src", | |
type: "src", | |
inputs: [ | |
{ | |
type: "sampler2D", | |
name: "tex", | |
default: NaN, | |
}, | |
], | |
glsl: ` vec4 c0 = texture2D(tex, wrap(_st)); | |
//c0 *= step(abs(_st.x-0.5),0.5); | |
//c0 *= step(abs(_st.t-0.5),0.5); | |
return c0;`, | |
}, | |
{ | |
name: "wrap", | |
type: "coord", | |
inputs: [], | |
glsl: `return wrap(_st);`, | |
}, | |
]; | |
hydraWrap.void = false; | |
hydraWrap.generateFunctionListFromWrapper = function (wrapper) { | |
return Array.from(hydraWrap.defaultList).map((_f) => { | |
let f = Object.assign({}, _f); | |
f.glsl = f.glsl.replace("wrap(_st)", wrapper); | |
f.glsl = hydraWrap.void ? f.glsl.replaceAll("//c0", "c0") : f.glsl; | |
return f; | |
}); | |
}; | |
hydraWrap.wrappers = { | |
wrap: "fract(_st)", | |
nowrap: "_st", | |
mirror: "-abs(fract(_st/2.0)*2.0-1.0)+1.0", | |
}; | |
hydraWrap.currentWrapper = hydraWrap.wrappers.wrap; | |
hydraWrap.setWrap = function () { | |
hydraWrap.void = false; | |
hydraWrap.currentWrapper = hydraWrap.wrappers.wrap; | |
hydraWrap | |
.generateFunctionListFromWrapper(hydraWrap.wrappers.wrap) | |
.forEach((x) => _hydra.synth.setFunction(x)); | |
}; | |
hydraWrap.setRepeat = hydraWrap.setWrap; | |
hydraWrap.setNoWrap = function () { | |
hydraWrap.void = false; | |
hydraWrap.currentWrapper = hydraWrap.wrappers.nowrap; | |
hydraWrap | |
.generateFunctionListFromWrapper(hydraWrap.wrappers.nowrap) | |
.forEach((x) => _hydra.synth.setFunction(x)); | |
}; | |
hydraWrap.setClamp = hydraWrap.setNoWrap; | |
hydraWrap.setMirror = function () { | |
hydraWrap.void = false; | |
hydraWrap.currentWrapper = hydraWrap.wrappers.mirror; | |
hydraWrap | |
.generateFunctionListFromWrapper(hydraWrap.wrappers.mirror) | |
.forEach((x) => _hydra.synth.setFunction(x)); | |
}; | |
hydraWrap.setCustom = function (wrapper = "_st") { | |
hydraWrap.void = false; | |
hydraWrap.currentWrapper = wrapper; | |
hydraWrap | |
.generateFunctionListFromWrapper(wrapper) | |
.forEach((x) => _hydra.synth.setFunction(x)); | |
}; | |
// setVoid should only be called after setting a wrapping mode | |
hydraWrap.setVoid = function (to = true) { | |
hydraWrap.void = to; | |
hydraWrap | |
.generateFunctionListFromWrapper(hydraWrap.currentWrapper) | |
.forEach((x) => _hydra.synth.setFunction(x)); | |
}; | |
hydraWrap.setWrap(); | |
} | |
// module.exports = loadExtensions; | |
window.setTimeout(() => loadExtensions(), 1000) | |
// ANTLIA by Ritchse | |
// https://github.com/ritchse/hydra-antlia | |
window.screenRatio = window.innerHeight / window.innerWidth | |
//basic shapes | |
window.circle = (s = .3, smooth = .007) => | |
solid(1, 1, 1, 1).mask(shape(256, s, smooth).scale(1, screenRatio)) | |
window.square = (s = .25, smooth = 0) => | |
solid(1, 1, 1, 1).mask(shape(4, s, smooth).scale(1, screenRatio)) | |
window.rectangle = function (s = .3, ratio = [1, 1], smooth = 0) { | |
var r; | |
if (ratio[0] > ratio[1]) | |
r = ratio[1]; | |
else | |
r = ratio[0]; | |
return solid(1, 1, 1, 1).mask(shape(4, s, smooth).scale(1 / r, screenRatio * ratio[0], ratio[1])); | |
} | |
window.triangle = function (s = .3, smooth = .007) { | |
if (typeof s === 'function') | |
yoffset = () => (0 - s() / 4); | |
else | |
yoffset = (0 - s / 4); | |
return solid(1, 1, 1, 1).mask(shape(3, s, smooth).rotate(Math.PI).scrollY(yoffset).scale(1, screenRatio)); | |
} | |
//strips | |
window.horiz = (s = .3, smooth = .0007) => | |
solid(1, 1, 1, 1).mask(shape(2, s, smooth)) | |
window.vert = (s = .3, smooth = .0007) => | |
solid(1, 1, 1, 1).mask(shape(2, s, smooth).rotate(Math.PI / 2)) | |
window.leftdiag = (s = .3, smooth = .0007) => | |
solid(1, 1, 1, 1).mask(shape(2, s, smooth).rotate(Math.PI / 4)) | |
window.rightdiag = (s = .3, smooth = .0007) => | |
solid(1, 1, 1, 1).mask(shape(2, s, smooth).rotate(Math.PI / -4)) | |
//quadrants | |
window.firstquad = (r=1,g=1,b=1,a=1) => | |
solid(r,g,b,a).mask(shape(4, 1, .0007).scale(.5).scroll(.25, .25)) | |
window.secondquad = (r=1,g=1,b=1,a=1) => | |
solid(r,g,b,a).mask(shape(4, 1, .0007).scale(.5).scroll(.25, -.25)) | |
window.thirdquad = (r=1,g=1,b=1,a=1) => | |
solid(r,g,b,a).mask(shape(4, 1, .0007).scale(.5).scroll(-.25, .25)) | |
window.fourthquad = (r=1,g=1,b=1,a=1) => | |
solid(r,g,b,a).mask(shape(4, 1, .0007).scale(.5).scroll(-.25, -.25)) | |
window.quad = function (i = 0, r=1,g=1,b=1,a=1) { | |
i %= 4 | |
switch (i) { | |
case 0: | |
return firstquad(r,g,b,a) | |
case 1: | |
return secondquad(r,g,b,a) | |
case 2: | |
return thirdquad(r,g,b,a) | |
case 3: | |
return fourthquad(r,g,b,a) | |
} | |
} | |
//extra | |
window.star = function (s = .3, v = 5, smooth = 0.007) { | |
if (typeof v === 'function') | |
i = () => (Math.PI * (v() / 2)); | |
else | |
i = Math.PI * (v / 2); | |
return solid(1, 1, 1, 1).mask(shape(1, -1, smooth).rotate(Math.PI / 8 * 7).kaleid(v).rotate(i).scale(s, screenRatio)); | |
} | |
window.grid = (x = 8, y = 4, b = 0.05, smooth = .001) => solid(1, 1, 1, 1).mask(shape(4, 1 - b, smooth).repeat(x, y).invert()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment