From cd6b217e495594b4aab854732c526930f762d613 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Sun, 21 Aug 2022 14:49:24 -0300 Subject: [PATCH 01/27] change code eval method to hydra sandbox --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index fa221ce..eedb37e 100644 --- a/script.js +++ b/script.js @@ -87,7 +87,7 @@ class CodeMirrorApp extends Torus.StyledComponent { this.evalCode = c => { try { - let result = eval(c); + let result = hydraApp.hydra.sandbox.eval(c); if (result === undefined) result = ""; this.console = result; this.consoleClass = "normal"; From af042bc275fb38fc084b451e081829b876aef070 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Sun, 21 Aug 2022 16:48:53 -0300 Subject: [PATCH 02/27] reset bpm, fps and speed on intersection --- script.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/script.js b/script.js index eedb37e..138935a 100644 --- a/script.js +++ b/script.js @@ -258,6 +258,9 @@ class CodeApp extends Torus.StyledComponent { (entries) => { if (entries[0].isIntersecting === true) { hush(); + bpm = 0; + fps = undefined; + speed = 1; solid(0, 0, 0, 0).out(o0); solid(0, 0, 0, 0).out(o1); solid(0, 0, 0, 0).out(o2); From 4587db1ea1d8bd61205b1170facf23e670216ad7 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Sun, 21 Aug 2022 18:26:46 -0300 Subject: [PATCH 03/27] typo on bpm --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index 138935a..1f28924 100644 --- a/script.js +++ b/script.js @@ -258,7 +258,7 @@ class CodeApp extends Torus.StyledComponent { (entries) => { if (entries[0].isIntersecting === true) { hush(); - bpm = 0; + bpm = 30; fps = undefined; speed = 1; solid(0, 0, 0, 0).out(o0); From 72b3f0d32f721faeec4558c48892e622d08d1431 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Sun, 21 Aug 2022 19:00:55 -0300 Subject: [PATCH 04/27] fix eval (use global context) --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index 1f28924..2d9450d 100644 --- a/script.js +++ b/script.js @@ -87,7 +87,7 @@ class CodeMirrorApp extends Torus.StyledComponent { this.evalCode = c => { try { - let result = hydraApp.hydra.sandbox.eval(c); + let result = window.eval(c); if (result === undefined) result = ""; this.console = result; this.consoleClass = "normal"; From f6a1c18addd405cba05afb8571e4e02ac3b4cc33 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Sun, 21 Aug 2022 22:15:15 -0300 Subject: [PATCH 05/27] styling for tables and tables of code --- style.css | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/style.css b/style.css index 621fb19..1f826e1 100644 --- a/style.css +++ b/style.css @@ -12,6 +12,18 @@ body, .sidebar { color: white; } +table{ + width: 100%; + table-layout: fixed; + padding: 0; + margin: 0; + background-color: #151515; +} + +th{ + background-color: black; +} + .CodeMirror { position: relative; margin: 0; @@ -84,6 +96,19 @@ p > img { border: none; } +.markdown-section table{ + display: table; +} + +.markdown-section tr:nth-child(2n) { + background-color: black; +} + +.markdown-section table tbody tr td pre[data-lang]{ + margin: 0; + border: 0; +} + .token.function { color: #99cc99; } From 3815c38572ee9eaefd02516dcef83a0655f96ceb Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Mon, 22 Aug 2022 10:40:27 -0300 Subject: [PATCH 06/27] apply background color to any code lang --- style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/style.css b/style.css index 1f826e1..b6c4d13 100644 --- a/style.css +++ b/style.css @@ -74,8 +74,8 @@ p > img { color: white; } -.markdown-section pre[data-lang=javascript], -.markdown-section pre[data-lang=javascript] > * { +.markdown-section pre[data-lang], +.markdown-section pre[data-lang] > * { background-color: #151515; } From 0ac4e76789f5939f6efad9205191832cd3ba9768 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Mon, 22 Aug 2022 15:20:36 -0300 Subject: [PATCH 07/27] remove output reset (already handled by hydra) --- script.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/script.js b/script.js index 2d9450d..2f35a0d 100644 --- a/script.js +++ b/script.js @@ -261,10 +261,6 @@ class CodeApp extends Torus.StyledComponent { bpm = 30; fps = undefined; speed = 1; - solid(0, 0, 0, 0).out(o0); - solid(0, 0, 0, 0).out(o1); - solid(0, 0, 0, 0).out(o2); - solid(0, 0, 0, 0).out(o3); render(o0); setTimeout(() => { this.cmApp.commands.evalAll(); From f3f4b08e9f955c4215940d0ed9925b18901505de Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 14:19:04 -0300 Subject: [PATCH 08/27] fix editor overflow --- script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/script.js b/script.js index 2f35a0d..bc56477 100644 --- a/script.js +++ b/script.js @@ -194,6 +194,7 @@ class CodeMirrorApp extends Torus.StyledComponent { z-index: 1; width: 100%; background-color: black; + overflow: hidden; } .editor-console { position: absolute; From 6310cb0c75932b46b04e8fd453b472d5e2aa018d Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 14:32:24 -0300 Subject: [PATCH 09/27] max out editor width when horizontal --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index bc56477..a10ddbb 100644 --- a/script.js +++ b/script.js @@ -171,8 +171,8 @@ class CodeMirrorApp extends Torus.StyledComponent { @media only screen and (max-width: 1200px) { position: relative; height: 10em; + max-width: 512px; } - max-width: 512px; display: flex; flex-direction: column; .editor-menu { From 4b2706e837750e373cd8f3c2c035cde747ca159c Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 14:40:05 -0300 Subject: [PATCH 10/27] higher observer threshold --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index a10ddbb..bf0fefc 100644 --- a/script.js +++ b/script.js @@ -269,7 +269,7 @@ class CodeApp extends Torus.StyledComponent { this.placeholder.appendChild(hydraApp.node); } }, - { threshold: [0.5] } + { threshold: [0.8] } ); observer.observe(this.placeholder); From 2a0bc79da91d95bc3751b36519ebb2b29280107d Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 14:55:08 -0300 Subject: [PATCH 11/27] fix editor horizontal overflow --- script.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script.js b/script.js index bf0fefc..ee546cb 100644 --- a/script.js +++ b/script.js @@ -168,6 +168,8 @@ class CodeMirrorApp extends Torus.StyledComponent { background-color: #444; width: 100%; height: 512px; + max-width: 100%; + overflow-x: hidden; @media only screen and (max-width: 1200px) { position: relative; height: 10em; From 8bbf3f3813382bf6ee5a8968c81eb79565a23c54 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 15:17:12 -0300 Subject: [PATCH 12/27] intersect when block touches center of screen --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index ee546cb..7fc8140 100644 --- a/script.js +++ b/script.js @@ -271,7 +271,7 @@ class CodeApp extends Torus.StyledComponent { this.placeholder.appendChild(hydraApp.node); } }, - { threshold: [0.8] } + { rootMargin: "-50% 0% -50% 0%", threshold: [0] } ); observer.observe(this.placeholder); From 8fac75d8b2f0895d6733d88699cfe763dd447ba1 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 15:26:29 -0300 Subject: [PATCH 13/27] reduce canvas size to 360 --- script.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/script.js b/script.js index 7fc8140..7e4cf20 100644 --- a/script.js +++ b/script.js @@ -5,14 +5,14 @@ class HydraApp extends Torus.StyledComponent { init() { this.canvas = document.createElement("CANVAS"); - this.canvas.width = 512; - this.canvas.height = 512; + this.canvas.width = 360; + this.canvas.height = 360; this.hydra = new Hydra({ canvas: this.canvas, detectAudio: false, enableStreamCapture: false, - width: 512, - height: 512, + width: 360, + height: 360, }); } styles() { @@ -167,13 +167,13 @@ class CodeMirrorApp extends Torus.StyledComponent { position: relative; background-color: #444; width: 100%; - height: 512px; + height: 360px; max-width: 100%; overflow-x: hidden; - @media only screen and (max-width: 1200px) { + @media only screen and (max-width: 1000px) { position: relative; height: 10em; - max-width: 512px; + max-width: 360px; } display: flex; flex-direction: column; @@ -285,12 +285,12 @@ class CodeApp extends Torus.StyledComponent { box-sizing: border-box; position: relative; width: 100%; - margin: 100px 0; + margin: 75px 0; display: flex; flex-direction: row; justify-content: center; align-items: stretch; - @media only screen and (max-width: 1200px) { + @media only screen and (max-width: 1000px) { flex-direction: column; flex-wrap: nowrap; align-items: center; @@ -298,9 +298,9 @@ class CodeApp extends Torus.StyledComponent { } .placeholder { position: relative; - min-width: 512px; - width: 512px; - height: 512px; + min-width: 360px; + width: 360px; + height: 360px; // display: flex; // justify-content: center; // align-items: center; @@ -310,7 +310,7 @@ class CodeApp extends Torus.StyledComponent { color: white; font-size: 1.25em; width: 100%; - max-width: 512px; + max-width: 360px; } `; } From d2843103b60f4a6855bf401f88b3f5750612ace2 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 15:29:25 -0300 Subject: [PATCH 14/27] change upper case transform --- style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/style.css b/style.css index b6c4d13..14e786a 100644 --- a/style.css +++ b/style.css @@ -62,7 +62,7 @@ p > img { font-weight: 700; } -.markdown-section h1, .markdown-section h2, .markdown-section h3, .markdown-section h4 { +.markdown-section h1, .markdown-section h2{ text-transform: uppercase; } @@ -80,7 +80,7 @@ p > img { } .markdown-section pre[data-lang=javascript].hydra-code { - margin-top: 512px; + margin-top: 360px; } .markdown-section code, .markdown-section pre { From 68b951ed37a260023e0085801f5b232745330415 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 15:30:39 -0300 Subject: [PATCH 15/27] add authors style --- style.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/style.css b/style.css index 14e786a..92d4318 100644 --- a/style.css +++ b/style.css @@ -24,6 +24,15 @@ th{ background-color: black; } +.authors { + font-style: italic; + font-size: smaller; + font-weight: 300; + text-align: right; +} + +/* codemirror */ + .CodeMirror { position: relative; margin: 0; From 006bf0d0a08c8170b379dd591063fbb1e9dc28c6 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 16:14:44 -0300 Subject: [PATCH 16/27] archive old guides --- additional_topics.md => archive/additional_topics.md | 0 in_depth_guides.md | 1 - 2 files changed, 1 deletion(-) rename additional_topics.md => archive/additional_topics.md (100%) delete mode 100644 in_depth_guides.md diff --git a/additional_topics.md b/archive/additional_topics.md similarity index 100% rename from additional_topics.md rename to archive/additional_topics.md diff --git a/in_depth_guides.md b/in_depth_guides.md deleted file mode 100644 index 2725107..0000000 --- a/in_depth_guides.md +++ /dev/null @@ -1 +0,0 @@ -# In-Depth Guides From f6e58bc3fba0d4efaa668a524798f636a034d182 Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 16:15:26 -0300 Subject: [PATCH 17/27] add gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d74e21 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode/ From 3489ba5719af8d81cb7b952e15eaa6cb12d3a3cd Mon Sep 17 00:00:00 2001 From: Renzo Torr- <56176668+ritchse@users.noreply.github.com> Date: Tue, 23 Aug 2022 16:15:58 -0300 Subject: [PATCH 18/27] add new guides --- _sidebar.md | 18 +- guides/advanced.md | 542 +++++++++++++++++++++++++++++++++++ guides/audio.md | 70 +++++ guides/embedding.md | 227 +++++++++++++++ guides/external_libraries.md | 113 ++++++++ guides/external_sources.md | 131 +++++++++ guides/glsl.md | 227 +++++++++++++++ guides/interactivity.md | 214 ++++++++++++++ guides/javascript.md | 314 ++++++++++++++++++++ guides/recording.md | 49 ++++ quick_reference.md | 271 ++++++++++++++++++ 11 files changed, 2173 insertions(+), 3 deletions(-) create mode 100644 guides/advanced.md create mode 100644 guides/audio.md create mode 100644 guides/embedding.md create mode 100644 guides/external_libraries.md create mode 100644 guides/external_sources.md create mode 100644 guides/glsl.md create mode 100644 guides/interactivity.md create mode 100644 guides/javascript.md create mode 100644 guides/recording.md create mode 100644 quick_reference.md diff --git a/_sidebar.md b/_sidebar.md index c4c4cbb..37b1c12 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -1,11 +1,23 @@ * [What is Hydra?](/) +* [API](https://hydra.ojack.xyz/api/) +--- * [Getting Started](/getting_started) +* [Quick Reference](/quick_reference) +--- * Guides - * [Additional Topics](/additional_topics) - * In-depth Guides -* [API](https://hydra.ojack.xyz/api/) + * [JavaScript Guide](/guides/javascript) + * [Advanced Guide](/guides/advanced) + * [Audio Guide](/guides/audio) + * [External Sources](/guides/external_sources) + * [External Libraries](/guides/external_libraries) + * [Interactivity Guide](/guides/interactivity) + * [GLSL Guide](/guides/glsl) + * [Recording Guide](/guides/recording) + * [Embedding Hydra](/guides/embedding) +--- * Contributing * [Code](/contributing_code) * [Translation](/contributing_translation) +--- * [Japanese | 日本語](/ja/README.md) * [使い方](/ja/getting_started.md) \ No newline at end of file diff --git a/guides/advanced.md b/guides/advanced.md new file mode 100644 index 0000000..d105a15 --- /dev/null +++ b/guides/advanced.md @@ -0,0 +1,542 @@ +# Advanced Guide +--- + +## Dynamic inputs + +If you're coding in Hydra, you're constantly trying many values to input to the sources and transforms, and it's just a matter of time until you like how more than one looks, and you want to somehow switch between them. We'll be referring to this idea of arguments whose value change over time as dynamic arguments. And there are two main ways to achieve this in Hydra: Arrays and functions. + +### Arrays + +#### Sequence your inputs + +When you send an Array as an input, Hydra will automatically switch and jump from each element from the Array to the next one. When there are no more elements, it wraps all the way back to the beginning. Let's see it in action: + +```hydra +osc([20,30,50,60],.1,[0,1.5]) + .out() +``` + +As you can see, the fact that both these Arrays have a different amount of values doesn't matter, Hydra will take values from each element of any Array for the same amount of time by default. + +The Arrays can be passed in any way, you may have a variable that stores an Array and use its name within your sketches (not recommended in some scenarios, more info below), you may create a function that returns Arrays and use that to automatically generate discrete sequences of values: + +```hydra +randomArray = (l=12)=> Array.from({length: l}, Math.random); +gradient() + .hue(randomArray()) + .out() +``` + +#### Changing the global bpm for Arrays + +To change how rapidly Hydra switches from element to element of any Array, you can change the `bpm` variable (meaning beats per minute) to any value you desire: + +```hydra +bpm = 138 // change me ! +randomArray = (l=12)=> Array.from({length: l}, Math.random); +gradient() + .hue(randomArray()) + .out() +``` + +The default value for `bpm` is 30. + +#### Changing the speed of a specific Array + +Hydra adds a couple of methods to all arrays to be used inside Hydra. `.fast` will control the speed of the array from which it is called. It receives a Number as argument, by which the global speed will be multiplied. So calling `.fast(1)` on an Array is the same as nothing. Higher values will generate faster switching, while lower than 1 values will be slower. + +```hydra +bpm = 45 +osc([20,30,50,60],.1,[0,1.5].fast(1.5)) + //.rotate([-.2,0,.2].fast(1)) // try different speeds for each array + .out() +``` + +#### Offsetting the timing of an Array + +Another one of the methods Hydra adds to Arrays, allows you to offset the timing at which Hydra will switch from one element of the Array to the next one. The method `.offset` takes a Number from 0 to 1. + +```hydra +bpm = 45 +osc([20,30,50,60],.1,[0,1.5].offset(.5)) // try changing the offset + .out() +``` + +#### Fitting the values of an Array within a range + +Sometimes you have an Array whose values aren't very useful when used as input for a some Hydra function. +Hydra adds a `.fit` method to Arrays which takes a minimum and a maximum to which fit the values into: + +```hydra +bpm = 120 +arr = ()=> [1,2,4,8,16,32,64,128,256,512] +osc(50,.1,arr().fit(0,Math.PI)) + .scale(arr().fit(1,2)) + .out() +``` + +#### Interpolating between values + +You can also interpolate between values instead of jumping from one to the other. That is, smoothly transition between values. For this you can use the `.smooth` method. It may take a Number argument (defaulted to 1) which controls the smoothness. + +```hydra +bpm = 50 +arr = [0,0.8,2] +osc(50,.1,arr.smooth()) + .rotate(arr.fit(-Math.PI/4,Math.PI/4).smooth()) + .out() +``` + +Try smoothing some of the above examples and see what happens! + +##### Easing functions + +The default interpolation used by Hydra on an Array that called `.smooth` is linear interpolation. You can select a different easing function as follows: + +```hydra +bpm = 50 +arr = [0,0.8,2] +osc(50,.1,arr.ease('easeInQuad')) + .rotate(arr.fit(-Math.PI/4,Math.PI/4).ease('easeOutQuad')) + .out() // try other easing functions ! +``` + +The following are the available easing functions: + +* linear: no easing, no acceleration +* easeInQuad: accelerating from zero velocity +* easeOutQuad: decelerating to zero velocity +* easeInOutQuad: acceleration until halfway, then deceleration +* easeInCubic +* easeOutCubic +* easeInOutCubic +* easeInQuart +* easeOutQuart +* easeInOutQuart +* easeInQuint +* easeOutQuint +* easeInOutQuint +* sin: sinusoidal shape + +#### Note on storing Arrays on variables + +Storing an Array in a variable can lead to some trouble as soon as you apply some of the just-mentioned functions to it. Since Arrays are Objects, each time you call your variable, you'll be calling the same Object. If you apply some speed via `.fast` or smoothness via `.smooth` somewhere in your patch, and then use the same variable, all the following uses of the Array will also have these effects applied to them. For example + +```hydra +arr = [1,2,3] +osc(30,.1,arr.smooth()) + .rotate(arr) + .out() + +arr2 = () => [1,2,3] +osc(30,.1,arr2().smooth()) + .rotate(arr2()) + .out(o1) + +render() +``` + +#### Note on Arrays and textures + +Note that the following will not work: + +```javascript +solid(1,.5,0) + .diff([osc(),noise()]) + .out() +``` + +Hydra can't handle Arrays of textures. You can work around it in some ways: + +```hydra +solid(1,.5,0) + .diff(osc().blend(noise(),[0,1].smooth())) + .out() +``` + +Unfortunately, if you want to use many textures this solution doesn't really apply. + +Users of Hydra have come up with some experimental solutions which might come in handy in some scenarios, but they come with some drawbacks: + +```javascript +// blending method, heavy GPU load. +// every element from the array will be rendered even if not shown. +// allows for blending between elements. + +select = function(arr,l=0){ + const clamp = (num, min, max) => Math.min(Math.max(num, min), max) + const blending = (l,i)=> (clamp(l-(i-1),0,1)) + const isFunction = (typeof l === 'function') + return arr.reduce((prev,curr,i)=> + prev.blend(curr, isFunction ? ()=>blending(l(),i) : blending(l,i)) + ) +} +textures = [noise(), osc(), voronoi(), gradient()] +select(textures,()=>Math.floor(mouse.x/innerWidth*4)) + .out() +``` + +```javascript +// re-compiling method, heavy CPU load. +// it reserves an output for the switching. +// can't blend between elements. +// each time an element switches the shader must be recompiled + +osc(20) + .rotate() + .modulate(o3,.2) + .out() + +textures = [noise(), osc(), voronoi(), shape()] +index = 0 +tex = textures[index] +update = (dt)=> { + if(time % (60 / bpm) * 1000 < dt){ + index++; index %= textures.length; + tex = textures[index] + tex.out(o3) + } +} +``` + +### Functions + +#### React to values in time + +The other main way of adding dynamic inputs to your sketches is passing functions as arguments. When Hydra takes a function as an argument, what it will do is evaluate it every time it renders a frame. The return of the function will be used as the value for that parameter during that frame render. So you can use a function to simply keep track of a value that you know will change over time, for example, mouse position (which we'll see later). + +```hydra +voronoi(5,.1,()=>Math.sin(time*4)) + .out() +``` + +The `time` variable seen there is a variable pre-declared by Hydra, that stores how much time passed since Hydra started in seconds. + +Functions used in Hydra don't need to be arrow functions, any no-argument function will do! Make sure your function is returning a Number to avoid errors. + +#### Using the time variable + +When you use functions that can take numerical arguments, `time` will allow you to have their values evolve through... time. If you multiply time by some value it's as if time goes faster, while dividing while act as making time go slower. For example `Math.sin(time*4)` will go 4 times faster than `Math.sin(time)`. + +Those users more familiar with mathematics might see this as: + +* `y(t) = t` : `()=>time` +* `y(t) = A sin(f t + ph)` : `()=>amplitude*Math.sin(freq*time + phase)` + +We recommend getting familiar with some of the methods in the JS built-in `Math` object. Learn more about it [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math) + +#### Changing the global speed + +You can either slow down or fasten the rate at with `time` increases via changing the `speed` variable: + +```javascript +speed = 1 // default +speed = 2 // twice as fast +speed = .5 // half as fast +speed = 0 // freezed +``` + +--- + +## Using iteration and conditionals to create patches + +##### Note + +For this tutorial we'll be assuming you've already learned by your own means what iteration and conditionals are in a programming context. + +### Iteration : automatically generate patches + +As you may know from regular programming, or other creative coding environments such as p5, iteration helps us repeat some operation(s) many times to achieve a specific goal. Maybe you would like to layer many similar objects but with slightly different values, and you want so many of them that writing each one manually isn't desirable. Maybe you want to have some form of very specific feedbacks, etc. +Let's jump straight into some examples. + +#### for loops + +For loops that generate patches can be used inside or outside functions, but we will be sticking with the latter for convenience. + +The typical structure of a patch-generating for loop is as follows: + +```javascript +someFunction = (iterations) => { + accumulator = osc(); // first part of the patch, a source + for(i=1; i { + nest = osc(freq,.02); step = Math.PI*2/div + for(r = step; r { + result = noise(4).luma(-.1); + for(i = 1; i 1 // innerHeight/innerWidth // this only makes sense in the editor +circle = () => shape(64,.9,.1).scale(1,screenRatio).luma().color(1,.1,.2) +tunnel = (q=5) => { + tunnel = circle(); + for(i=1; i {return char.charCodeAt()}) +result = solid() +arr.forEach(ascii => { + result.add( + shape(4,.2,.5).luma(.6,.3) + .color(1,0,0).hue(ascii/100) + .scrollX(.4+ascii/100) + .rotate(Math.PI*ascii/100) + ,.4) +}) +result.out() +``` +Try changing the text, and remember not to use very long strings given they will be quite heavy to process. + +##### .reduce + +Using `.reduce` is quite useful when you have an array of textures. Here's a simple example: + +```hydra +vs = [noise(), osc(), voronoi(), gradient()]; +result = () => vs.reduce((prev,curr)=> { + return prev.diff(curr) + },solid()) +result().out() +``` + +##### .map + +Haters of state (non-political) will prefer `.map` any day over `.forEach`. Looking at the example for `.forEach`, we see were creating a texture and adding it to an accumulator _for each_ element in the array. We can separate the texture generating part of the code and the blending part using `.map` to get an array of textures and `.reduce` to blend them: + +```hydra +text = "Hydra >:]" +arr = Array.from(text).map(char => {return char.charCodeAt()}) +arr = arr.map(ascii => + shape(4,.2,.5).luma(.6,.3) + .color(1,0,0).hue(ascii/100) + .scrollX(.4+ascii/100) + .rotate(Math.PI*ascii/100) +) +result = arr.reduce((prev,curr)=>prev.add(curr,.4),solid()) +result.out() +``` + +### Conditionals + +Conditionals aren't very useful on their own here, given all code execution on Hydra happens arbitrarily and manually via the interaction of the user. The only case you would want to use an `if` statement by its own while livecoding Hydra is that where you'd like some variable to change given some condition and only at the time of each code evaluation. But even still, you'll see that putting any conditionals inside functions will be the most useful approach because of code reusability and readability. Let's get to it. + +#### Conditionals in functions + +We know from previous tutorials we can make our own functions to be used as arguments of Hydra sources and transforms, and how Hydra evaluates these functions each frame. Here's an example where we use conditionals to have a hue change happen only during 3 seconds out of every 10 seconds: + +```hydra +hueChange = () => { + if(time%10 < 3) + return time/2 + else + return 0 +} +osc(20,.1,2.6) + .modulate(osc(20).rotate(Math.PI/2),.3) + .hue(hueChange) + .out() +``` + +Another common use of conditionals in programming is to avoid errors or undesired behaviors. Here's a simple example where we wrap the square root function from the Math API into our own `sqrt` function which turns any negative input into positive: + +```hydra +sqrt = (n) => { + if(n<0) n*=-1; //if negative, multiply by -1 + return Math.sqrt(n) +} +noise(2) + .diff(noise(2).scale(()=>sqrt(Math.sin(time/2)))) + .out() +``` + +#### The ternary operator + +Before we go forward and use both iteration and conditionals, we'd like to show you the ternary operator. This operator can simplify many conditional operations. The syntax is the following: + +```javascript +x = condition ? valueIfTrue : valueIfFalse; + +// which is the same as +if(condition) + x = valueIfTrue +else + x = valueIfFalse +``` + +Now we can simplify the hue change example into: + +```javascript +osc(20,.1,2.6) + .modulate(osc(20).rotate(Math.PI/2),.3) + .hue(()=> time%10<3 ? time/2 : 0) + .out() +``` + +#### Conditionals inside iterations + +Let's go back to a previous example, the nest, where we wanted to do many `diff` using the same oscillator many times with different angles of rotation. Here's a new version where we invert the colors of the first half of oscillators, and we apply colorama to the oscillator in every other iteration. + +```hydra +// 'nest' is only a creative, arbitrary name +nest = (freq,div) => { + nest = osc(freq,.02); step = Math.PI*2/div + for(i = 1; i-inv) + .invert(()=>inv) + .diff(osc(30).rotate(()=>r)) + .out() + +update = (dt) => { + inv = time % 12 < 6 ? Math.abs(Math.sin(time)) : time*2 % 2 + r += Math.sin(time/2) * (time%2.5) * 0.02 + r -= Math.cos(time)*0.01 +} +``` + +The structure `time % every < duration` is super useful to make stuff happen `every` certain amount of seconds, for a given `duration` (also in seconds). +`time % wavelength / wavelength` can be interpreted as a sawtooth wave with a given wavelength. This would generate an ascending sawtooth going from 0 to 1 in the amount of time specified by whe wavelength. For a descending one you can write something like `1-(time % wavelength / wavelength)`. If you want values from 0 to `wavelength` just remove the division. + +#### Adding a frame counter to make frame-specific actions + +Having something appear for only one frame can be super useful in many feedback-based sketches: + +```hydra +toggle = 0; rotation = .01 +src(o0) + .scale(1.017) + .rotate(()=>rotation) + .layer( + osc(10,.25,2) + .mask(shape(4,.2)) + .mult(solid(0,0,0,0),()=>1-toggle) + ) + .out() + +frameCount = 0 +update = (dt) => { + toggle = 0 + if(frameCount % 120 == 0){ + toggle = 1; rotation *= -1; + } + frameCount++ +} +``` + +#### Randomly evolving values through time (Random walker) + +```hydra +x = 0; y = 0; +t = ()=> solid(1, 1, 1, 1).mask(shape(3, .05, .01).rotate(Math.PI)) +src(o0) + .blend(osc(8,.1,.2).hue(.3),-.015) + .scale(1.01) + .rotate(.01).mult(solid(0,0,0),.006) + .layer(t().scroll(()=>x,()=>y)) + .layer(t().scroll(()=>-x,()=>-y)) + .out() +update = (dt)=> { + x += (Math.random()-.47)/100 + y += (Math.random()-.47)/100 +} +``` + +### `update` vs Arrow functions + +Every function that you use as an argument is evaluated right before the current frame is about to be rendered. Which is the same thing that happens with the `update` function! This means, unless we use `dt`, everything we can do on `update` we can technically do on argument functions. It's up for us to decide when one's better than the other. If we are controlling many interconnected variables and procedures, most probably, an arrow function or a named function won't be that nice to use. It'll be confusing as to why a function which is supposed to represent a simple dynamic argument is doing so much stuff inside of itself. Maybe we could separate the different behaviors into many arrow functions. But if these functions were to feed from each other, this will yet again get confusing quite rapidly. Even then, there are many scenarios where an arrow function can do the same work as the update function with less code. For example, here's a patch where a circle chases your mouse: + +```hydra +// works well only on editor +x = () => (-mouse.x/innerWidth)+.5 +y = () => (-mouse.y/innerHeight)+.5 +posx = x(); posy = y(); +shape(16,.05) + .scrollX(() => posx += (x() - posx) / 40) + .scrollY(() => posy += (y() - posy) / 40) + .add(o0,.9) + .out() +``` \ No newline at end of file diff --git a/guides/audio.md b/guides/audio.md new file mode 100644 index 0000000..d49a9ca --- /dev/null +++ b/guides/audio.md @@ -0,0 +1,70 @@ +# Audio Guide +--- + +## Reacting to audio + +In order to achieve audio reactivity, Hydra makes use of a JavaScript library called Meyda and has a pre-defined object called `a` to access many of its features. Audio reactivity in Hydra is mainly achieved using an algorithm called Fast Fourier transform. You definitely don't need to know what it is or how it achieves what it does to use it, but you need to understand the following: + +### The audio spectrum + +Sound travels through air as a wave, that's fairly common knowledge. This basically means that sound is nothing more than air pressure going up and down through time very fast in weird ways. But we don't experience sound simply as something that goes on and off like a light flickering, many of the sounds we are used to have some sort of frequency or repetition that we interpret as higher or lower pitch. +When we listen to a song we can easily differentiate the bass guitar from the singer even if both are playing at the same time. If there are two vocals being sung at the same time, even if by the same person, we can differentiate them because of how high or low they are (also because of timbre, but that doesn't matter at all right now). +When we talk about the audio spectrum, we are talking about the many frequencies a sound can cover and we humans can hear. +What a fast fourier transform does is basically hear some ongoing sound and interpret how present the sound is on different parts of this spectrum. For example, if we separate the audio spectrum in 3 equal parts and play a drumkit, the bass drum will have more presence on the lower side of the spectrum, while a hi-hat will surely have most of its presence on the higher third part of the spectrum. + +### a.show() & a.hide() + +We can see what Hydra hears by calling the function `a.show()`. This will show a small graphic on the lower right corner of the screen. If we want to hide it we can call `a.hide()` + +### a.setBins() & a.fft + +We can decide into how many parts we want to separate the audio spectrum using the function `a.setBins()`. As you might've already guessed, a bin is just a part of the audio spectrum. Try now to use very high values since more bins means more processing and you can go overkill easily. But also, you'll see there usually isn't much need to separate the spectrum into that many parts. +To read the current value of a bin we use an Array (a list of variables per se) called `a.fft`. + +See the following example: + +```javascript +a.setBins(5) +osc(20,.1,2) + .saturate(()=>1-a.fft[4]) + .rotate(()=>a.fft[0]) + .kaleid() + .out() +``` + +See how if you make a deep "O" sound into the mic, the rotation will be strong and the saturation won't be affected as much. Also try to make a high "S" sound, you'll see the exact opposite. + +Note how we use brackets to call an element in an array, and that we start counting from 0. + +#### a.bins & a.prevBins + +If you want the raw values from each bin without the mapping from 0 to 1, you can access them via `a.bins`. You can also use `a.prevBins` to get the bins from the previous frame. + +### a.setSmooth() + +Sometimes the audio reactive elements react... too much. You can easily get into strobe territory if you are not careful. You can smooth out the interpretation of the sound using the `a.setSmooth()` function. A value of 0 will be no smoothing at all, a raw input, while a value of 1 will be so smooth nothing will happen at all. Try evaluating `a.setSmooth(.85)` above and see how different it looks. + +### a.setScale() & a.setCutoff() + +Each microphone, each sound input, etc, can be quite different in volume and dynamics. If you're on a noisy room, your visuals could react to the noise and that's quite annoying. Or if your mic is too low on volume, your visuals may barely react. +If you ran `a.show()` and look at the graph, you may have noticed there are 2 horizontal lines going across the bins. The lowest one represents the cutoff (guitar players and other musicians out here might know this as a noise gate), this means that the value of the bin will be 0 unless the sound goes above that cutoff. The higher line is the scale, that's where the maximum value of 1 is (again, musicians may want to see this as a limiter with auto-gain). If a given bin goes above the scale value, its value won't go past 1. + +### a.vol + +`a.vol` will give you the overall volume of the audio input. + +### a.onBeat() & a.beat + +Hydra also has a simple beat detection algorithm. You can change this function to anything you like and it will be executed whenever Hydra detects a beat. The beat detection algorithm uses values from `a.vol` and compares them with a threshold set at `a.beat`. + +`a.beat` stores the configuration for the beat detection Hydra uses. It's an Object which most useful parameter is `threshold`. `a.beat.threshold` represents the volume to which `a.vol` will be compared to detect a beat. There's also `a.beat.decay` which sets the decay after a beat. + +### Reacting to music + +As we've seen, Hydra takes your microphone as an input, not your desktop audio. Those interested in using a music player or a DAW's output as an audio input will have to delve into virtual audio routing. + +--- + +## Virtually routing audio to Hydra + +TODO \ No newline at end of file diff --git a/guides/embedding.md b/guides/embedding.md new file mode 100644 index 0000000..d77e0ec --- /dev/null +++ b/guides/embedding.md @@ -0,0 +1,227 @@ +# Embedding Hydra on a website +--- + +## First steps + +### HTML + +To include the Hydra synth (bundled version) in your website, add the following script tag to your page's head. + +```html + +``` + +Now, given a `script.js` file where you want to include your Hydra code: + +```html + + +``` + +### JS + +You can start Hydra as such: + +```js +const hydra = new Hydra([opts]) +``` + +Where `opts` is an Object containing the options to start Hydra with. Default values shown below. + +```javascript +{ + canvas: null, // canvas element to render to. if none, it'll be created automatically + width: 1280, // defaults to canvas width when included + height: 720, // defaults to canvas height when included + autoLoop: true, // if true, will automatically loop. if false, request frames using tick() + makeGlobal: true, // if false, will not pollute global namespace (experimental) + detectAudio: true, // setting this to false avoids asking for microphone + numSources: 4, // number of external source buffers to create initially + numOutputs: 4, // number of output buffers to use. (more than 4 can make render() unpredictable) + extendTransforms: [], // array of transforms to add to the synth, or an object representing one + precision: null, // 'highp', 'mediump', or 'lowp'. defaults to highp for ios, and mediump otherwise. + pb: null, // instance of rtc-patch-bay to use for streaming +} +``` + +Then, if `makeGlobal` is set to `true`, you can write Hydra code like you would in the editor. + +### Examples + +For example, `script.js` might contain the following code: + +```javascript +let canvas = document.createElement("canvas"); +canvas.width = 512; +canvas.height = 512; +canvas.id = "hydra-canvas"; +document.body.appendChild(canvas); + +const hydra = new Hydra({ + canvas: canvas, + detectAudio: false, + enableStreamCapture: false, +}); + +osc(4, 0.1, 1.2).out() +``` + +#### Instance mode + +If you don't want to pollute the global namespace with Hydra's functions, or you think some of them might conflict with other library you're using, setting `makeGlobal` to `false` will start Hydra in instance mode. For example: + +```javascript +let canvas = [...] // same as above + +const hydra = new Hydra({ + canvas: canvas, + makeGlobal: 512, + detectAudio: 512, + enableStreamCapture: false, +}); +const h = hydra.synth + +h.osc(4, 0.1, 1.2).out() +h.src(h.o0).blend(h.noise(3)).out(h.o1) +h.render(h.o1) +``` + +##### Multi-Hydra + +Instance mode also makes it possible to use more than one hydra canvas at once: +```javascript +const h = new Hydra({ makeGlobal: false, detectAudio: false }).synth +h.osc().diff(h.shape()).out() +h.gradient().out(h.o1) +h.render() + +const h2 = new Hydra({ makeGlobal: false, detectAudio: false }).synth +h2.shape(4).diff(h2.osc(2, 0.1, 1.2)).out() +``` + +See https://glitch.com/edit/#!/multi-hydra for a working example of multiple hydra canvases, created by Naoto Hieda. + +##### Make global a select group of functions + +To keep the same syntax as hydra in non-global mode, consider destructuring the object further. This can also allow to expose only a handful of the functions in Hydra. +```javascript +const { src, osc, gradient, shape, voronoi, noise, s0, s1, s2, s3, o0, o1, o2, o3, render } = hydra +shape(4).diff(osc(2, 0.1, 1.2)).out() +``` + +## Hydra as a website's background + +### HTML + +```html + + +
+

Title

+ Some text :) +
+ +``` + +### CSS + +```css +#hydra-bg { + position: fixed; /* ignore margins */ + top: 0px; + left: 0px; + width: 100%; /* fill screen */ + height: 100%; + background-size: cover; + overflow-y: hidden; + z-index: -1; /* place behind everything else */ + display: block; +} + +.content { + background-color: rgba(0,0,0,0.3); + margin: 6%; +} +``` + +Changing the z-index to a very high value, the size of the canvas to something small (make sure to change it in JS as well), and the pixel position to wherever you desire, would effectively change the website to one where Hydra is an overlay. + +### JS + +```javascript +let hydraCanvas = document.getElementById("hydra-bg"); +// set small size to avoid high resource demand: +hydraCanvas.width = Math.min(window.innerWidth / 2, 1280); +hydraCanvas.height = Math.min(window.innerHeight / 2, 720); + +const hydra = new Hydra({ + canvas: hydraCanvas, + detectAudio: false, + enableStreamCapture: false, +}); + +osc().blend(noise()).out(); +``` + +#### Note on scrolling + +You may find useful using `window.scrollY` (or X) inside your background's Hydra sketch for it to change as you scroll. The event listener `onscroll` might also be helpful. + +#### Note on resize + +You may want to automatically resize the canvas' size as the user resizes the window: + +```javascript +resizeTimeout = -1; +onresize = ()=> { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(()=>{ + setResolution( + Math.min(window.innerWidth / 2, 1280), + Math.min(window.innerHeight / 2, 720) + ); + },200) +} +// using timeouts to avoid setting the resolution constantly while the user is resizing +``` + +### Live example + +You can find a live example [here](https://hydra-background-webpage.glitch.me/). You can open the glitch editor for it [here](https://glitch.com/edit/#!/hydra-background-webpage). + +## Scrollable Hydra webpage with different codeblocks + +Given a webpage with a Hydra background (just like the described above), and a series of code blocks to be evaluated across the website's scroll size, you can create a new IntersectionObserver for each codeblock so that its code is run whenever the user scrolls to it. For example: + +```javascript +const codeblocks = document.querySelectorAll("code"); + +let initialized = false; + +for(const cb of codeblocks) { + if(initialized == false) { // run the first codeblock if hydra wasn't initialized + eval(cb.textContent); + initialized = true; + } + + var observer = new IntersectionObserver(function (entries) { + if (entries[0].isIntersecting === true) { + // hush(); // reset outputs on codeblock change + render(o0); + setTimeout(()=>{ + eval(cb.textContent) + }, 60); + } + }, { threshold: [0.7] }); + + observer.observe(cb); +} +``` + +### Live example + +You can find a live example [here](https://hydra-scrollable-webpage.glitch.me/). You can open the glitch editor for it [here](https://glitch.com/edit/#!/hydra-scrollable-webpage). + +### Live example with moving canvas + +There's a version of the last example where Hydra's canvas moves to the next codeblock, just like it happens on this website. You can find a live example [here](https://hydra-long-webpage.glitch.me/). You can open the glitch editor for it [here](https://glitch.com/edit/#!/hydra-long-webpage). \ No newline at end of file diff --git a/guides/external_libraries.md b/guides/external_libraries.md new file mode 100644 index 0000000..2f34eef --- /dev/null +++ b/guides/external_libraries.md @@ -0,0 +1,113 @@ +# External Libraries +--- + +## Loading external libraries and extensions + +In the Hydra editor, you can load any external scripts, libraries or hydra-synth extensions using the following syntax at the top of your sketch: + +```javascript +await loadScript("https://www.somewebsite.com/url/to/hydra-script.js") +``` + +You can also suffer from [CORS policy problems](./external_sources.md#cors-policy) if the script/package you're loading doesn't come from a CDN. If you want to load from a GitHub or GitLab repo, you can use special CDNs like `statically.io`. + +--- + +## Using p5 inside Hydra + +p5 is pre-loaded on the Hydra editor with a wrapper that makes it easier to use inside the website. The wrapper is a class called `P5` (notice the upper-case P). + +```javascript +p1 = new P5() // first, load p5 in instance mode +``` + +You can also specify some settings: + +```javascript +p1 = new P5({width: 512, height: 512, mode: 'P2D'}) +``` + +Now the p5 canvas is overlaying the Hydra canvas. You can hide it by running: + +```javascript +p1.hide() // p1.show() to revert +``` + +And you may load it to a source to use p5's canvas as one: + +```javascript +s0.init({src: p1.canvas}) +``` + +### p5.setup() + +When live coding, the `setup()` function of p5 has basically no use; anything that you would have called in setup() you can simply call outside of any function. For example: + +```javascript +p1.noStroke() +p1.fill(255, 0, 100) +``` + +### p5.draw() + +Now, to set a `draw` loop simply use all the functions and variables you are used to on global p5 from the variable you're using: + +```javascript +p1.draw = () => { + p1.fill(p1.mouseX/5, p1.mouseY/5, 255, 100) + p1.rect(p1.mouseX, p1.mouseY, 30, 30) +} +``` + +### Livecoding + +You can technically call any p5 function while livecoding. So you can draw anything onto screen on evaluation instead of using the `draw` loop. + +```javascript +p1.clear() + +for(let i = 0; i < 50; i++) + p1.rect(20, 20, p1.width/50*i, p1.height/50*i) +``` + +### Using Hydra's render loop + +You can stop p5's own looping and do your p5 actions inside Hydra's render loop via the `update` function. This will synchronize p5's and Hydra's frame renders. + +```javascript +p1.noLoop(); + +p1.clear() +p1.colorMode(p1.HSB) +p1.stroke(0) +p1.strokeWeight(1) + +src(o0) + .scale(1.05) + .blend(src(o0).brightness(-.02),.4) + .modulateHue(o0,100) + .layer(s0) + .out() + +p1.draw = () => { + if(p1.random() < 0.01) p1.clear() + p1.fill(time*100%200, 70, 100) + p1.rect(p1.random()*p1.width, p1.abs(p1.sin(time*2))*p1.height, 50, 50) +} + +update = (dt)=> { + p1.redraw(); +} +``` + +You could also use shape drawing functions such as `rect` directly inside `update`, but you'll need to take into account the coordinate system won't be reset automatically if modified, like when using `draw`. So you'll have to reset it manually by putting actions between `push()` and `pop()`. This would also stop the `frameCount` increment. + +### Note on using different frame rates + +There are many situations where you can save resources by using a very low frame rate on p5 and a high one on Hydra or vice-versa. For example, if you want to place random shapes on the p5 canvas every second, you can set p5's `frameRate` to 1 and leave Hydra's fps undefined. + +--- + +# Using THREE.js inside Hydra + +TODO \ No newline at end of file diff --git a/guides/external_sources.md b/guides/external_sources.md new file mode 100644 index 0000000..f603bb9 --- /dev/null +++ b/guides/external_sources.md @@ -0,0 +1,131 @@ +# External Sources +--- + +## initCam + +You can use a webcam's video as such: + +```javascript +s0.initCam() + +s0.initCam(2) // if you have many cameras, you can select one specifically +``` + +## initScreen + +You can capture your screen or specific windows or tabs to use as a video source: + +```javascript +s0.initScreen() +``` + +--- + +## initImage + +In order to load an image to load an image into a source object, the syntax is the following: + +```javascript +s0.initImage("https://www.somewebpage.org/urlto/image.jpg") +``` + +When running Hydra in Atom, or any other local manner, you can load local files referring to them by URI: + +```javascript +s0.initImage("file:///home/user/Images/image.png") +``` + +### Supported formats + +You can load `.jpeg`, `.png`, and `.bmp` as well as `.gif` and `.webp` (although animation won't work). + +## initVideo + +The syntax for loading video is the same as for loading image, only changing the function to `loadVideo`: + +```javascript +s0.initVideo("https://www.somewebpage.org/urlto/video.mp4") +``` + +### Supported formats + +You can load `.mp4`, `.ogg` and `.webm` videos. + +### Useful HTML Video properties + +You can access all of the [HTML Video](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) functions when a video is loaded to a Source via `s0.src`. Some useful properties are: + +```javascript +s0.src.playbackRate = 2 // double the speed at which the video plays +s0.src.currentTime = 10 // seek to the 10th second +s0.src.loop = false // don't loop the video +``` + +--- + +## initStream : streaming between Hydra sessions + +Hydra (the editor) also has built-in streaming. You can stream the output of your Hydra to someone else and vice-versa. This is done in a similar fashion to using images and videos, using external sources. But there are some extra steps for streaming: + +### The pb object + +On your Hydra editor, you can find a pre-defined object called `pb` (as in patch-bay). This object basically represents the connection of your Hydra editor instance to all others hosted on the same server. When you want to share your stream to someone else you'll have to give your Hydra session a name. Do this using the `pb.setName()` function and by passing in some string as the name. For example: `pb.setName('myverycoolsession')`. If you want someone else to stream to you, ask them to set a name as such and share it with you. + +You can see online sessions using the function `pb.list()`, which will return an array of names. + +### Starting to stream + +Streaming is as simple as initiating the source as a stream and passing the name of the session you want to stream. For example: + +```javascript +s0.initStream('myfriendsverycoolsession') +src(s0) + .out() +``` + +--- + +## Extra parameters + +Any external sources loaded into Hydra are using [regl's texture constructor](https://github.com/regl-project/regl/blob/master/API.md#textures) in the background. There are many properties you can set when loading a texture and Hydra and regl handle the important ones for you. But to set any of these properties you can pass an object containing them to any of the init functions. For example: + +```javascript +s0.initCam(0,{mag: 'linear'}) +``` + +`mag` & `min` are the most used, since using `linear` interpolation will resize textures in a smooth way. The default for both is `nearest`. + +--- + +## Common problems + +### CORS policy + +If you try to load images (or videos) from some websites (most of them, really), sometimes nothing shows up on the screen. Opening the browser's console might reveal a message similar to this one: + +``` +Access to image at '...' from origin 'https://hydra.ojack.xyz' +has been blocked by CORS policy: +No 'Access-Control-Allow-Origin' header is present on the requested resource. +``` + +The CORS in CORS policy stands for 'Cross-origin resource sharing'. This refers to the action of calling resources (such as images) from one website to another. For example, asking for an image hosted on other website from inside the Hydra editor. This error message is basically telling us "hey, the website you're trying to ask for an image doesn't allow other websites to use their resources, so i can't let you have that picture". +In order to circumvent this error, you can try re-uploading the images you want to use to some image hosting service that allows cross-origin sharing such as imgur, where you can also load short videos. You can also try to use websites which you know will allow cross-origin resource sharing such as [Wikimedia Commons](https://commons.wikimedia.org/), which is great for video. + +### Loading video from YouTube, Vimeo, etc + +Some users may be tempted to try and load some video they liked on YouTube, for example, and run something suchlike: + +```javascript +s0.initVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ') // doesn't work +``` + +This will not work. The same goes for Vimeo and other video streaming services. When you use such an URL, it is not returning a video, it is returning the website where you can watch the video! The URL you pass to `initVideo` has to go directly to a video file. In other words, the URL should (usually) end in `.mp4`, `.webm` or `.ogg`. And, even if you did get a URL directly to the video with a tool such as youtube-dl, you'll run into CORS problems. + +#### Workaround + +The most common workarounds are: + +* Run Hydra locally (on Atom for example) and load local video files +* Have the video run on its own window and use `initScreen` to capture it + diff --git a/guides/glsl.md b/guides/glsl.md new file mode 100644 index 0000000..d71280e --- /dev/null +++ b/guides/glsl.md @@ -0,0 +1,227 @@ +# GLSL Guide + +## Using custom GLSL functions + +Those more experienced with Hydra and/or digital visuals in general, might know that Hydra is built on WebGL and its shadering language, GLSL ES. Hydra has a unique way of adding custom source and transform functions which we will explain here. + +### setFunction + +The Hydra API includes a function called `setFunction` which receives a specific type of JavaScript object. This object will have the properties `name`, `type`, `inputs` and `glsl`. +* `name` is a String with the name for the function +* `type` is one of the available types of functions ('src', 'color', 'coord', 'combine', 'combineCoord') +* `inputs` is an Array of objects each with it's own `name`, `type` and `default` properties. They represent the arguments of the GLSL function. +* `glsl` is a String with the glsl code. + +#### Example + +```javascript +setFunction({ + name: 'myOsc', + type: 'src', + inputs: [ + { name: 'freq', type: 'float', default: 20 } + ], + glsl: ` + return vec4(sin((_st.x+time)*freq*vec3(0.1)),1.0); + ` +}) +``` + +### Types of GLSL functions and their arguments + +#### src + +A function with a specified type of `src` is one that generates visuals by its own. Just like `osc` or `noise`. They all have a `vec2` argument called `_st` for the coordinate. And you can add any custom inputs as shown above. You must return a `vec4`. + +#### color + +A `color` function receives a `vec4` called `_c0` that represents the color being affected by the transform. As any function you may add any extra inputs. You must return another `vec4`. + +```hydra +setFunction({ + name: 'switchColors', + type: 'color', + inputs: [], + glsl: ` + return _c0.brga; + ` +}) +osc(60,.1,5).switchColors() + .out() +``` + +#### coord + +A `coord` function receives a `vec2` called `_st` that represents the coordinate plane. You must return another `vec2`. + +```hydra +setFunction({ + name: 'tan', + type: 'coord', + inputs: [ + { name: 'freq', type: 'float', default: 1 }, + { name: 'mult', type: 'float', default: 0.25 } + ], + glsl: ` + return tan(_st*3.141592*freq)*mult; + ` +}) +osc(60,.1,5).tan(2) + .out() +``` + +#### combine + +The functions of type `combine` receive 2 `vec4` arguments, `_c0` and `_c1`. The first one represents the texture being affected and the latter represents the texture being blended into the former. For example, when you use `osc().mult(noise())`, inside the definition of the function, `_c0` represents the `osc()` and `_c1` represents the `noise()` colors. You can think combine functions as blending modes. And as custom function you may add extra inputs as needed. You must return a `vec4`. + +```hydra +setFunction({ + name: 'negate', + type: 'combine', + inputs: [ + { type: 'float', name: 'amount', default: 1 } + ], + glsl:` + _c1 *= amount; + return vec4(vec3(1.0)-abs(vec3(1.0)-_c0.rgb-_c1.rgb), min(max(_c0.a, _c1.a),1.0)); + ` +}) +osc().negate(noise().brightness(.5)) + .out() +``` + +#### combineCoord + +`combineCoord` functions change the position of colors in the texture being affected given the colors of another texture. Think about the many modulate functions for example, since they are precisely this type. They receive a `vec2 _st` and a `vec4 _c0`. You must return a `vec2`. + +```hydra +setFunction({ + name: 'myModulator', + type: 'combineCoord', + inputs: [], + glsl: ` + return vec2(_st.x+(_c0.g-_c0.b*0.1),_st.y+(_c0.r*0.2)); + ` +}) +noise(2).myModulator(osc(20,.1,1).diff(o0)) + .out() +``` + +### Built in functions you can use + +The following functions are pre-defined for every Hydra generated shader, and in the same way that some built-in functions use them, you may too: + +#### _luminance + +```glsl +float _luminance(vec3 rgb){ + const vec3 W = vec3(0.2125, 0.7154, 0.0721); + return dot(rgb, W); +} +``` + +Returns the luminance of a given rgb color. + +#### _rgb2Hsv + +```glsl +vec3 _rgbToHsv(vec3 c){ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} +``` + +Transforms a color from the rgb to the hsv colorspace. + +#### _hsv2Rgb + +```glsl +vec3 _hsvToRgb(vec3 c){ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} +``` + +Transforms a color from the hsv colorspace back to rgb. + +##### Note + +As of now there is no way to define "global" functions such as these ones just mentioned. But expect it soon! + +### Examples + +#### Chroma Key + +This example modifies `color` to replace green background with transparency (i.e., chroma keying). The GLSL code is ported from [Inigo Quilez's example](https://www.shadertoy.com/view/XsfGzn). + +```javascript +setFunction({ + name: 'chroma', + type: 'color', + inputs: [ + ], + glsl: ` + float maxrb = max( _c0.r, _c0.b ); + float k = clamp( (_c0.g-maxrb)*5.0, 0.0, 1.0 ); + float dg = _c0.g; + _c0.g = min( _c0.g, maxrb*0.8 ); + _c0 += vec4(dg - _c0.g); + return vec4(_c0.rgb, 1.0 - k); +`}) + +// s0.initCam() +// src(s0).out(o0) +solid(0,1,0).layer(shape(5,0.3,0.3).luma()).out(o0) +osc(30, 0, 1).layer(src(o0).chroma()).out(o1) +render() +``` + +--- + +## GLSL Injection + +Since Hydra runs GLSL on the background, and everything you input into the parameters of the different functions ends up written on GLSL (be it literally or as a uniform), you can sort of hack Hydra (and totally break it) by sending strings of GLSL expressions as arguments: + +```hydra +src(o0) + .scroll(()=>mouse.x/innerWidth,()=>mouse.y/innerHeight) + .layer(noise(3).sub(osc(5,.1,2)).luma(.1,.01)) + .scroll("-sign(st.x-0.5)*0.5","-sign(st.y-0.5)*0.2") + .scale("0.8+st.x*0.2") + .rotate(0,.1) + .brightness(.01) + .out() +``` + +The reason you can "totally break Hydra" here is that Hydra works with a modular flow. In order for it to work, when you do coordinate transforms after a bunch of interconnected textures, these transforms must apply to all coordinate references in the shader. If you inject values of the `st` coordinates in your arguments, Hydra has no way of applying any further transforms to them, therefore breaking the modularity. + +--- + +## Extensions + +### Extra shaders + +There are some Hydra extensions that load many custom glsl functions, such as: + +* [extra-shaders-for-hydra](https://gitlab.com/metagrowing/extra-shaders-for-hydra) +* [hydra-blending-modes](https://github.com/ritchse/hydra-extensions) + +### Extra Functionality + +The [hydra-glsl extension](https://github.com/ritchse/hydra-extensions/blob/main/doc/hydra-glsl.md) allows you to write GLSL directly in your patches. For example: + +```javascript +glsl('vec4(sin(((_st.x*54.)+time*2.)*vec3(0.1,0.102,0.101)),1.0)') + .diff(o0) + .glslColor('vec4(c0.brg,1.)') + .glslCoord('xy*=(1.0/vec2(i0, i0)); return xy',.25) + .glslCombine('c0-c1',o1) + .glslCombineCoord('uv+(vec2(c0.r,c0.b)*0.1)',o1) + .out() +``` \ No newline at end of file diff --git a/guides/interactivity.md b/guides/interactivity.md new file mode 100644 index 0000000..c1e7f19 --- /dev/null +++ b/guides/interactivity.md @@ -0,0 +1,214 @@ +# Interactivity Guide +--- + +## Using the mouse + +You can have your visuals react to the position of your mouse (or finger, in touch devices). Hydra has an object called `mouse` which stores and keeps track of the position of your mouse on the webpage. + +### mouse.x & mouse.y + +```javascript +gradient() + .hue(()=>mouse.x/3000) + .scale(1,1,()=>mouse.y/100) + .out() +``` + +You can refer to the pixel position of your mouse by calling `mouse.x` and `mouse.y`, each one corresponding to the horizontal and vertical coordinates respectively. When we say 'pixel position', this means that the values you'll find stored in both x and y are represented in pixels. So for `mouse.x`, this means the amount of pixels from the left edge of your window to the position of your mouse. For `mouse.y`, this means the amount of pixels between the top end of your screen and the position of your mouse. + +Many times it will be most useful to use values relative to the size of the screen. And also to have values that exist between ranges more reasonable to the hydra functions you're using. For example [-0.5; 0.5] for scrollX and scrollY, [0; 2pi] for rotation, or [0; 1] for general purposes. + +### Control anything with your mouse + +On Hydra, most values used are pretty small. So it will be way more useful to have the position of the mouse as values from 0 and 1: + +#### Getting values from 0 to 1 + +```javascript +x = () => mouse.x/innerWidth // 0→1 +y = () => mouse.y/innerHeight // 0→1 +osc() + .scale(()=>1+x()*2) + .modulate(noise(4),()=>y()/4) + .out() +``` + +You can simply multiply by `2*Math.PI` to change the range to [0; 2pi] + +### Make something follow your mouse + +On Hydra, things are placed between 0.5 and -0.5 (left to right, top to bottom). In order for anything to follow your mouse, you'll need to get the position of your mouse between that range: + +#### Getting values from 0 to ±0.5 from the center + +```javascript +x = () => (-mouse.x/innerWidth)+.5 // 0.5→-0.5 +y = () => (-mouse.y/innerHeight)+.5 // 0.5→-0.5 +solid(255) + .diff( + shape(4,.1) + .scroll(()=>x(),()=>y()) + ) + .out() +``` + +Remember you can name these functions however you prefer. + +--- + +## Using JavaScript event listeners + +Browsers have implemented in them a system of events which allows them to do X when Y happens. More concretely, this means we can define functions that will be run whenever a given event happens. Some examples of events are `click` (when a mouse click happens), `resize` (when the browser's window is resized), `keydown` (when a keyboard key is pressed down), `load` (when the webpage and its resources have been loaded), etc. +We call each function we assign to an event an "event listener". And there are to ways to add event listeners to our Hydra instance (or in any webpage): + +```javascript +addEventListener('click', (event) => {}); + +onclick = (event) => { }; +``` + +We'll be using the latter in the next examples since it's shorter. The former can allow you to add many listeners for the same event, but we don't really need that as much in Hydra, although you might find cool things to do with them! +All event listeners have one parameter, which here we conveniently called `event`, where we receive an object representing the event and useful data about it. For example, a `click` event includes data about the position the click was done, if the alt or ctrl keys were pressed while clicking, etc. You can ignore it if you won't use values from it. + +### click, pointerdown and pointerup + +One of the key things you need to take into account when working with mouse and keyboard events is that every time you click, every time you press a key, there's a pressing and a release action. These are usually named in events as 'down' or 'up'. For example, `mousedown` events happen when you start pressing the mouse button, `mouseup` events will happen at the release, and `click` events will happen when you both pressed and release the left mouse button. However, we'll try to use `PointerEvent`'s' instead of `MouseEvent`s since they are more general and work perfectly with any sort of pointer such as touch devices, drawing tablets, etc. They also allow for multi-touch input. + + +#### Toggle transforms on click + +```javascript +toggle = 0 +onclick = () => { toggle = toggle ? 0 : 1 } +osc(30,.2,1.5) + .rotate(()=>toggle*Math.PI/2) + .out() +``` +Note that in JavaScript, you can pass any value as a boolean and JavaScript will try to interpret it in some way (search for truthy and falsy if you want to learn more). With numbers, `0` will act as `false` and any other number will act as `true`. We can make use of this to generate our toggling action. +Fans of bitwise operators might implement the toggle as `toggle = toggle ^ 1`, where `^` means XOR. + +##### Pointer version + +We can simplify a click to be simply when any pointer is released (or pressed, whichever you prefer): +```javascript +onclick = null // getting rid of the conflicting click event +// +toggle = 0 +onpointerup = () => { toggle = toggle ? 0 : 1 } +osc(30,.2,1.5) + .rotate(()=>toggle*Math.PI/2) + .out() +``` +Note that this example won't work if run after the previous one. Since both events will be triggered and the toggle will happen two times. Remember to "clean-up" any conflicting events either by reloading Hydra (remember to save doing `Ctrl+Shift+L`!) or simply setting the conflicting event to `null`. For example: `onclick = null`. + +#### Activate transforms while clicking + +```javascript +press = 0 +onpointerdown = () => { press = 1 } +onpointerup = () => { press = 0 } +osc(30,.2,-1) + .kaleid(()=> press ? 6 : 2 ) + .modulate(o0,.085) + .out() +``` +Note how we make use of the ternary operator again to get arbitrarily different values depending on if the user is pressing or not. You could also change these values into some other functions! or play with `time`. + +#### Count the amount of clicks + +```javascript +clicks = 1 +onpointerup = () => { clicks++ } +noise(()=>clicks) + .out() +``` +The use of `++` at the end of `clicks` is a shorthand in many C-like languages for something like `clicks = clicks + 1`. Learn more about it [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Increment) + +#### Accumulate during clicks + +In order to accumulate values while a pointer is being pressed we need to be constantly checking if it's pressed. We can do this using JavaScript's functionality for intervals. We could even do this with timeouts, actually. But there's a better way of doing this, which is checking if the pointer is being pressed every time Hydra renders a frame. But let's not get ahead of ourselves, and see the intervals version before: + +```javascript +press = false +onpointerdown = () => { press = true } +onpointerup = () => { press = false } +pressedTime = 0; sensitivity = 0.05; +// clearInterval(interval) // uncomment after first eval +interval = setInterval(()=>{ pressedTime += press ? sensitivity : 0 },34) +osc(15,.1,()=>pressedTime) + .out() +``` + +Ok, now that we see how tedious it can be to use intervals, let's see how we can do this in a more Hydrated way. As we just said, we could try checking if the pointer is being pressed each time Hydra renders a frame. Coincidentally, the arrows functions we love to use as arguments for our functions, are checked by Hydra every frame! We could try writing the action that updates our `pressedTime` inside one: + +```javascript +press = false +onpointerdown = () => { press = true } +onpointerup = () => { press = false } +pressedTime = 0; sensitivity = 0.05; +osc(15,.1,()=>pressedTime += press ? sensitivity : 0) + .out() +``` +This works! And there's no need to explicitly `return pressedTime` in our function since JavaScript can infer that's what we want to return since it's the last (and only) assignment we made. +There's another way of making Hydra run some functionality each frame, which is using Hydra's `update` function which is discussed in the next part. + +#### Generating new patches on clicks + +Since outputting is simply a function call as any other. We can also try to evaluate patches when an event is triggered: +```javascript +textures = [noise(),osc(),osc(50).kaleid(),voronoi(),noise().thresh(0)] +onpointerdown = () => { + osc(40,.05,Math.random()*2) + .rotate(Math.PI/2) + .modulate(textures.at(Math.random()*textures.length),.25) + .out() +} +osc().out() // placeholder until click +``` + +#### Other mouse/pointer events + +There are many other pointer events you can try to experiment with! For example `pointermove` is triggered whenever the pointer moves. Read more about them [here](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events#event_types_and_global_event_handlers) + +### keydown & keyup + +To use keyboard input, we can use `KeyboardEvent`'s such as `keydown`, `keyup`. These work exactly like `pointerdown` and `pointerup`, but here's where the listener parameter we called `event` earlier, the one we've been quietly ignoring, comes into play. Since the event that we get will tell us which key was pressed! And if it was pressed while pressing Ctrl or Alt, etc. Now we can do everything we've been doing with the mouse buttons using any key from the keyboard! If we want to use a specific we can just use a condition and ask if the event we received is correspondent with the key we want. You can test different keys and what their events return in very handy websites such as [key.js](https://keyjs.dev/). + +#### Moving stuff with the arrow keys + +Here either add or subtract one from our x and y values depending on which arrow key the user presses. Each representing one of the 2 dimensional axis'. +```javascript +x = 0; y = 0; +onkeyup = (event) => { + switch(event.key){ + case "ArrowUp": y++; break; + case "ArrowDown": y--; break; + case "ArrowLeft": x++; break; + case "ArrowRight": x--; break; + } +} +noise(6) + .diff(o0) + .scroll(()=>x/10,()=>y/10) + .sub(shape(4,.02).scroll(()=>x/10,()=>y/10),2) + .out() +``` + +#### Example: Typing shapes + +Here we use the `keyCode` of the last pressed key to change the shape being shown. This is a modification of ax example from the page about iteration. There we used ASCII codes to automatically generate shapes. However, ASCII and keyCodes of given letters are different! + +```javascript +key = 25 +onkeydown = (e) => { key = e.keyCode } +src(o0) + .scale(1.007) + .layer( + shape(()=>3+(key%4),.1,.4) + .luma(.6,.01) + .color(1,.5,0).hue(()=>key/100) + .scrollX(()=>.4+key/100) + .rotate(()=>Math.PI*key/100) + ) + .out() +``` \ No newline at end of file diff --git a/guides/javascript.md b/guides/javascript.md new file mode 100644 index 0000000..255f909 --- /dev/null +++ b/guides/javascript.md @@ -0,0 +1,314 @@ +# JavaScript Guide + +This guide is made for users who are new to JavaScript or coding in general and would like to dive into these topics. You don't need to fully understand what's here to use Hydra. If you're just starting with Hydra and you have no coding experience, we recommend you experiment with Hydra a bit before reading this. + +## Comments + +```javascript +// This is a one line comment +``` + +Most programming languages have implemented in them a feature commonly referred as comments. These are ways to write annotations into your code without having the machine interpret them as code. JavaScript, the scripting language that Hydra works on, has implemented comments in the same tradition as many other languages such as Java or C. You use `//` for single line comments, and you can use `/* ... */` for multi-line comments. + +```javascript +/* + An example of a multi line commentary: + This sketch shows an oscillator: +*/ +osc().out() +``` + +You can also write comments at the end of lines of code too, which is very useful while annotating what's going on with your visuals sometimes: + +```javascript +noise(2,.5) + .diff(src(o0).rotate(Math.PI/4)) // rotating by 45° + .thresh(.5) + .color(1,.1,.3) // pink color + .out() +``` + +You will surely find useful sometimes to "comment in and out" some lines of code to see how it affects the visuals, or simply to understand what each line of code does. By adding a `//` at the start of a line you can comment it out and see how some sketch would look like without a given transform without having to delete the original line. + +```javascript +noise(2,.5) + .diff(src(o0).rotate(Math.PI/4)) // rotating by 45° + //.thresh(.5) + .color(1,.1,.3) // pink color + .out() +``` + +--- + +## Variables + +Variables are spaces of memory in your computer that you reserve to store some value. Each variable you use will have a unique symbolic name. This definition may sound complicated, but you'll see it's really as intuitive as it can be. You may remember variables from mathematics being letters that represent some sort of number. This is precisely the same, you just choose some name and assign some number (or other type of information) to it. + +```javascript +freq = 50 // change this value and see what happens +osc(freq) + .diff(osc(freq).rotate()) + .out() +/* + the above is equivalent to: + osc(50) + .diff(osc(50).rotate()) + .out() +*/ +``` + +In the previous example, `freq` is the name of the variable and `50` is its value. + +Variable names can't start with numbers, they start with letters and it's conventional in JavaScript to start with a lowercase. When the name of your variable is more than one word, it's also conventional to write them as such: + +```javascript +feedbackIntensity = .3 +src(o0) + .colorama(feedbackIntensity/10) + .scale(.96) + .layer(noise().luma(.1)) + .out() +``` + +However this is just a convention, you may find other ways of naming your variables more useful. You may even like to use only one letter variables (such as `x`, `y`, etc), it's faster to code but harder for others to understand. Find your own balance and style. + +### Global variables + +When you declare a variable in Hydra, it declares it for you on the global scope. You can imagine a scope as a piece of code that works on its own and has its own variables. However, the global scope is basically a bunch of variables and functions that can be accessed from anywhere (functions such as `osc()` are declared in the global scope so that you can use them by just calling them, no matter where, for example). +We can make it explicit that we want something on the global scope. In JavaScript, since it's made to run on a browser, we do this by declaring variables on the `window` object (what is an object, you can find out below), which represents the browser's window. + +```javascript +window.globalVariable = 21.4 + +osc(globalVariable).out() +``` + +However, you can drop the `window.` part since the default behavior is the same: + +```javascript +globalVariable = 21.4 + +osc(globalVariable).out() +``` + +If you see JavaScript code elsewhere you'll surely see the keywords `let` or `const`. These define variables on their scope. So avoid them if you want to declare variables that can be freely used while livecoding. + +```javascript +let scopedVariable = 21.4 +// this will only work if evaluated on the same block +osc(scopedVariable).out() +``` + +This knowledge will come in handy if you start coding functions for example, since each function has its own scope, and if you want to declare something on the global scope, you'll have to be explicit about it. + +--- + +## Arrays + +Arrays are basically lists of values. Instead of declaring 100 variables to represent different values of the same concept you can just use a list of values. The key thing is these values are related, they will serve the same purpose somewhere in our code. If they are not related, using a list isn't really useful, we'll be just confusing ourselves thinking about where in the list did we put this or that other value. Here's an example of an array: + +```javascript +rots = [0,.5,1.2,2.2,3] // this is how we declare an array +osc().rotate(rots[0]) // this is how we call an element from an array + .diff(osc().rotate(rots[2])) + .out() +``` + +The example above isn't that useful in a Hydra context, but we hope it illustrates the basics of how an array is created and used. +Arrays in JavaScript (and in most programming languages) start counting their elements from 0 and not from 1. So if you want the first element of the `rots` array, you need to call `array[0]` instead of `array[1]`! Same goes for every element. If you want the third element call `array[2]`, and so on. Remember that nth element = array[n-1] + + +Arrays in Hydra can be used to create dynamic inputs. + +--- + +## Functions + +A function is similar to a variable in the sense that you're going to give it its own name and call it multiple times later. The difference being that functions do not store values, they store pieces of code that -usually- return some value. +You can see them as little boxes where you put something in and they spit something out. +Functions will help you not to repeat your code multiple times, sometimes you'll see you can write a function that spits out what you need instead of rewriting it many times. + +### Defining functions + +There are multiple ways to define functions in JavaScript, here's an example of a function named `sum` that takes two numbers called `a` and `b` and returns (spits out) the sum of both numbers: + +```javascript +function sum(a,b){ + return a+b; +} + +sum = function(a,b){ + return a+b; +} + +sum = (a,b) => a+b +``` + +We'll be sticking with the last form of defining functions, usually called 'arrow function'. It is worth noting the first form it's a bit like using `let` and `const` for variables, they work on their own scope. + +#### Local variables in functions + +Talking about scope, you may want to define variables inside your functions, which are local to the functions and aren't variables that should be used globally. Now the `let` keyword becomes useful. + +```javascript +sum = function(a,b){ + let result = a+b; + return result; +} + +sum = (a,b) => a+b +``` + +### Functions that return a texture + +```javascript +circle = () => shape(64,.4,.1).scale(1,innerHeight/innerWidth) +circle() + .out() +``` + +Now you may be thinking "Wait, shouldn't this simply be a variable that stores that texture? If there's no input what's the use of having a function here?". And in a way you would totally be right. Except for the fact that if you use a variable to store that shape, you'll be always referring to the exact same object that represents that shape. If you use a variable `circle` and apply some transforms to it somewhere in your patch, and try to use circle again later, all the transforms that you applied will be there! Because you applied those to that object precisely. Also, even if you don't apply any transforms, JavaScript can be very messy when referencing the same object multiple times in some situations. So, if you use a function, each time you call it a new object representing that texture will be created. +Another reason we would use a function in this example is that if we want to add some input to this function, well, it's already a function so we can do it. + +Let's see how we could make the circle function more useful by adding parameters: + +```javascript +circle = (size,blur=.1)=> shape(64,size,blur) + .scale(1,()=>innerHeight/innerWidth) +circle(.4) + .diff(circle(.2,.6)) + .out() +``` + +Now, each time we call the circle function we can specify a size and blur. We can also omit the blur and the function will use the default value specified next to it. +We also changed the scaling to an arrow function, which you may find surprising if you haven't seen it before. When you use a function as an argument, Hydra will evaluate that function every time it renders a frame and use the return of that function in the rendering of that frame. In other words, functions can be used as dynamic inputs. + +### Using declared functions as inputs + +As we just mentioned, we can use arrow functions inside the arguments of a given source or transform for it to react in real time. If you have many arguments using the same arrow function, you may want to declare it and reuse its name: + +```javascript +scaling = () => .9+(Math.sin(time*2)/3) +noise(3) + .scale(scaling) + .layer(src(o0).scale(scaling).mask(shape())) + .out() +``` + +#### Calling declared functions from other functions + +Sometimes you want to reuse a function but have something change about it. For example, maybe we want to make the scaling negative for the feedback in the last example. But calling `-scaling` doesn't make sense, at least to JavaScript, since the negative of a function doesn't exist. But the negative of its return might: + +```javascript +scaling = () => .9+(Math.sin(time*2)/3) +noise(3) + .scale(scaling) + .layer(src(o0).scale(()=>-scaling()).mask(shape())) + .out() +``` + +You'll also come across this if your function has an input. For example: +```javascript +scaling = (multiplier)=> (.9+(Math.sin(time*2)/3))*multiplier +``` +Doing `.scale(scaling)` doesn't make sense anymore, since you aren't giving it its necessary input. And if you try to do `.scale(scaling(-1))`, Hydra will evaluate the function once and use its return as the input to scale, instead of using a function which is what we want for the visual to react to the changes in time. The solution is, again, a function that calls your function, such as `.scale(()=>scaling(-1))`. If for some reason you hate arrow functions, you could also try [binding it](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) doing `.scale(scaling.bind(0,1))`. + +### Higher-order functions + +Higher order functions just means functions that take other functions as arguments. These are useful when you want to make u new functions which take behavior from other functions. As an example, let's visualize applying a sine function (with some tweaks) to itself: + +```javascript +fps=40 +twice = (func,...args) => func(func(...args)) +myFunc = (x)=> Math.sin(x*2)*2 +src(o0) + .luma(.02) + .mult(solid(),.02) + .add(shape(32,.02).scroll(0,()=>0.25+myFunc(time)/50,-.2)) + .add(shape(32,.02).scroll(0,()=>-.25+twice(myFunc,time)/50,-.2)) + .out() +``` + +That new `...args` thing simply takes all the arguments sent to a function, we use it so we can call whatever function sent with as many arguments as it needs. +Take into account `twice(myFunc,time)` is the same as `myFunc(myFunc(time))`, and you may prefer to write the latter in many occasions. But you can also send an arrow function to `twice`, which could save you declaring a functions you may only want to use once, or writing the same declaration twice. + +--- + +## Objects + +You can imagine an object as a special variable, which instead of containing a value, it contains other variables and functions. The former are usually called properties of an object and the latter are methods of an object. +For example, if you ever use Hydra on instance mode, what you'll come across is Hydra as a special object containing all the functions you know and love, instead of having them on the global scope. + +Let's see an example of how to declare and use an object with some properties: + +```javascript +coords = {x: 0.25, y: 0.25} // this is how we declare an object with some properties +shape(8,.1) + .scroll(coords.x,coords.y) // this is how we call properties + .out() +``` + +And now let's add a method: + +```javascript +coords = { + x: 0.25, + y: 0.25, + inverted: function(){ + newCoords = {x: -this.x, y: -this.y} + return newCoords + } +} +shape(8,.1) + .scroll(coords.inverted().x,coords.inverted().y) + .out() +``` + +There's a new keyword that we hadn't seen before here: `this`. The `this` keyword is used in methods (functions of an object) to refer to the object from which the method is called. + +Objects can also be conceptualized as dictionaries, with keys and values. The keys would be the names of the properties (and methods) of the object and the values is what they store: + +```javascript +numbers = { + pi: 3.14159265359, + e: 2.71828182846, + golden: 1.61803398875 +} +numbers['pi'] // another way we can call keys from an object +``` + +### Useful properties in the window object + +The window object has lots of information about the environment that our visuals run on. You'll see lots of Hydra sketches that make use of them, more commonly for example, the `innerWidth` and `innerHeight` properties. These properties store the respective width and height that the webpage occupies on your screen. + +For example, we can calculate the ratio between height and width to have perfect squares on our sketches: + +```javascript +// this example will only work on the editor or atom-hydra +screenRatio = innerHeight/innerWidth +shape(4,.4).scale(1,screenRatio) + .out() +``` + +There's also the less used `screenX` and `screenY` which will tell you the position of the window relative to the full screen. Try to resize the window and move it around with the following example: + +```javascript +osc() + .rotate(()=>20+(screenX/300)) + .out() +``` + +--- + +## The Math Object + +You have surely seen many examples in Hydra and in these tutorials that make use of mathematical functions such as the sine wave. You may have also noticed that each time one of them is used, they're written as `Math.somefunction()`. The reason for this is that all these very useful functions are taken from a special object called `Math` that is present in practically every JavaScript implementation. +You can see the full list of functions and variables in the Math object [clicking here](https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Math). + +### Math.PI + +One of the most useful predefined variables that the Math API has is the value of pi (well, an approximation considering that pi has infinite decimals). Many Hydra functions take radians as arguments which you may know are usually represented using multiples of pi. For example, if you want to rotate a texture exactly half a pi (90 degrees), you can do it as such: + +``` +osc().rotate(Math.PI/2).out() +``` diff --git a/guides/recording.md b/guides/recording.md new file mode 100644 index 0000000..4be652d --- /dev/null +++ b/guides/recording.md @@ -0,0 +1,49 @@ +# Recording +--- + +## Saving images from Hydra + +You can press `Ctrl+Shift+S` to save a screenshot of your Hydra's canvas. You can also do this programmatically calling the function `screencap()` + +--- + +## Hydra's built-in recorder + +You can very easily record a video evaluating the following commands: + +```javascript +vidRecorder.start() // run this to start recording + +vidRecorder.stop() // run this to stop recording and download video +``` + +Videos recorded with this method are recorded and downloaded with the vp9 codec and webm filetype. However, they can also be quite low quality. + +--- + +## OBS + +[OBS](https://obsproject.com/) is the preferred recording method by most Hydra users. It's a free and open source software for video recording and live streaming available on practically all platforms. + +### How to use OBS + +1. Download and open OBS +2. In the Controls pane on the right side, click on Settings +3. Go to the Output tab, then to the Recording pane and set your preferences. Then click on OK. +4. On the Sources pane, click on the + icon and select Window Capture. If you want to, give the new source a name and click OK +5. Set the Window to the browser your running Hydra on. Most users also prefer to unselect the Capture cursor setting. Click OK. +6. On the preview at the middle of the screen, select your source, right click and then click on Resize output (source size), select Yes. +7. Set your audio input's volume (or mute it) on the Audio Mixer pane. +8. On the Controls pane, click Start Recording. When you want to stop, click Stop Recording. + +#### Trim the browser's UI + +If you don't want to record your browser's UI, do the following: +1. Right click your source and click on Filters. +2. Click on the + icon on the lower left corner and select Crop/Pad. Click OK. +3. Set the values until your UI disappears. Sometimes you only need to set the Top value. +4. Click OK + +#### Note on window size + +If you want to set your browser's window to a specific size, there are various add-ons on different browsers that allow you to do that. Remember you want to set the size of the view pane and not the whole window. \ No newline at end of file diff --git a/quick_reference.md b/quick_reference.md new file mode 100644 index 0000000..238af0c --- /dev/null +++ b/quick_reference.md @@ -0,0 +1,271 @@ +# Quick Reference + +## Reset Hydra + +```hydra +hush() + +// fps, bpm and speed won't be reset to their defaults by calling hush +// their defaults are: fps = undefined, bpm = 30, speed = 1 +``` + +## Load images / videos / camera / screen + +```hydra +s0.initImage('https://hydra.ojack.xyz/docs/assets/hydra.jpg') +src(s0) + .out() + +// for video: +// s0.initVideo('https://website.com/somevideo.mp4') + +// for webcam: +// s0.initCam(0) + +// for screen: +// s0.initScreen() + +// change interpolation method (default is 'nearest'): +// s0.initImage('https://hydra.ojack.xyz/docs/assets/hydra.jpg',{min:'linear', mag:'linear'}) +``` + +Further explanation in the [external sources guide](guides/external_sources). + +## Arrays + +```hydra +bpm = 40 + +src(o0) + .scale([1,1.02]) + .layer(osc(9,.1,2).mask(shape(4,.3,0))) + .out(o0) + +osc(30,.1,[0,1.5].fast(1.5)) + .diff(shape(16,[0,.3],.1)) + .out(o1) + +osc() + .rotate([1,4,2,5,3].fit(0,3.14).smooth()) + .out(o2) + +sh = ()=> [.2,.5,.7,.8].ease('easeInQuart') +noise(2) + .shift(sh() + ,sh().offset(1/3) + ,sh().offset(2/3)) + .out(o3) + +render() +``` + +Further explanation in the [advanced guide](guides/advanced#arrays). + +### Easing functions + +* linear: no easing, no acceleration +* easeInQuad: accelerating from zero velocity +* easeOutQuad: decelerating to zero velocity +* easeInOutQuad: acceleration until halfway, then deceleration +* easeInCubic +* easeOutCubic +* easeInOutCubic +* easeInQuart +* easeOutQuart +* easeInOutQuart +* easeInQuint +* easeOutQuint +* easeInOutQuint +* sin: sinusoidal shape + +## Arrow functions + +```hydra +voronoi(5,.1,()=>Math.sin(time*4)) + .out() +``` + +Further explanation in the [advanced guide](guides/advanced#functions). + +## Mouse + +```hydra +gradient() + .hue(()=>mouse.x/3000) + .scale(1,1,()=>mouse.y/2000) + .out(o0) +``` + +```javascript +// getting values from 0 to 1 +x = () => mouse.x/innerWidth // 0 → 1 +y = () => mouse.y/innerHeight // 0 → 1 + +// getting values from 0.5 to -0.5 (left to right) +x = () => (-mouse.x/innerWidth)+.5 // 0.5 → -0.5 +y = () => (-mouse.y/innerHeight)+.5 // 0.5 → -0.5 +``` + +## Audio + +```javascript +a.setBins(5) // amount of bins (bands) to separate the audio spectrum + +noise(2) + .modulate(o0,()=>a.fft[1]*.5) // listening to the 2nd band + .out() + +a.setSmooth(.8) // audio reactivity smoothness from 0 to 1, uses linear interpolation +a.setScale(8) // loudness upper limit (maps to 0) +a.setCutoff(0.1) // loudness from which to start listening to (maps to 0) + +a.show() // show what hydra's listening to +// a.hide() + +render(o0) +``` + +Further explanation in the [audio guide](guides/audio) + +## `update` + +```hydra +toggle = 0; rotation = .01 +src(o0) + .scale(1.017) + .rotate(()=>rotation) + .layer( + osc(10,.25,2) + .mask(shape(4,.2)) + .mult(solid(0,0,0,0),()=>1-toggle) + ) + .out() + +frameCount = 0 +update = (dt) => { + toggle = 0 + if(frameCount % 120 == 0){ + toggle = 1; rotation *= -1; + } + frameCount++ +} +``` + +Further explanation in the [advanced guide](guides/advanced#using-the-update-function). + +## Streaming between sessions + + + + + + + + + + + + + + +
Hydra 1Hydra 2
+ +```javascript +pb.setName('hydra_1') + +s0.initStream('hydra_2') +osc() + .diff(src(s0)) + .out() +``` + + + +```javascript +pb.setName('hydra_2') + +s0.initStream('hydra_1') +noise() + .blend(s0) + .out() +``` + +
+ +Further explanation in the [external sources guide](guides/external_sources#streaming-between-hydra-sessions). + +## Custom GLSL functions + +```hydra +setFunction({ + name: 'tan', + type: 'coord', + inputs: [ + { name: 'freq', type: 'float', default: 1 }, + { name: 'mult', type: 'float', default: 0.25 } + ], + glsl: ` + return tan(_st*3.141592*freq)*mult; + ` +}) +osc(60,.1,5).tan(2) + .out() +``` + +Further explanation in the [glsl guide](guides/glsl). + +### Types of GLSL functions + +
+ +| Type | Prameters | Return type | +|------|--------|-| +| src | _st | vec4 | +| color | _c0 | vec4 | +| coord | _st | vec2 | +| combine | _c0, _c1 | vec4 | +| combineCoord | _st, _c0 | vec2 | + +
+ +## Loading external libraries + +```javascript +await loadScript("https://unpkg.com/tone") + +synth = new Tone.Synth().toDestination(); + +bpm = 60 +rot = 0 +osc(25).diff(osc(20).rotate(()=>rot)) + .out() + +update = (dt)=> { + if ((time % (60/bpm))*1000 < dt){ + rot = Math.random()*Math.PI*2; + synth.triggerAttackRelease(220+Math.random()*660, "8n"); + } +} +``` + +Further explanation in the [external libraries guide](guides/external_libraries). + +## p5 integration + +```javascript +p1 = new P5() + +p1.hide() // hide the p5 canvas +s0.init({src:p1.canvas}) // assign s0 to p5 canvas + +src(s0) + .diff(osc(3)) + .out() + +p1.draw = ()=> { + p1.background(0,10) + p1.rect(p1.mouseX,p1.mouseY,120,40) +} +``` + +Further explanation in the [external libraries guide](guides/external_libraries). \ No newline at end of file From d55a881caa21318d9029c6583405745797c490be Mon Sep 17 00:00:00 2001 From: Renzo Torrisi <56176668+ritchse@users.noreply.github.com> Date: Wed, 24 Aug 2022 01:31:36 -0300 Subject: [PATCH 19/27] fix can't intersect last codeblock via padding --- style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/style.css b/style.css index 92d4318..0e72f39 100644 --- a/style.css +++ b/style.css @@ -62,6 +62,10 @@ p > img { margin-right: auto; } +.markdown-section{ + padding-bottom: calc(50vh - 360px - 75px + 5px); +} + .markdown-section em { color: #cccccc; } From 3e99facd38b45de72794c8a2a2217018235cbe6a Mon Sep 17 00:00:00 2001 From: Renzo Torrisi <56176668+ritchse@users.noreply.github.com> Date: Wed, 24 Aug 2022 01:56:36 -0300 Subject: [PATCH 20/27] add glsl highlighting --- index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 5022245..83790ee 100644 --- a/index.html +++ b/index.html @@ -70,8 +70,10 @@
- + + + From dba49919c75a1915d372c7f5cc2ce89989664b58 Mon Sep 17 00:00:00 2001 From: Renzo Torrisi <56176668+ritchse@users.noreply.github.com> Date: Wed, 24 Aug 2022 02:13:26 -0300 Subject: [PATCH 21/27] defer scripts --- index.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 83790ee..6626402 100644 --- a/index.html +++ b/index.html @@ -64,16 +64,16 @@ crossorigin="anonymous" > + + + + +
- - - - - From 1f45635716935464f343b267710356b56e092426 Mon Sep 17 00:00:00 2001 From: Renzo Torrisi <56176668+ritchse@users.noreply.github.com> Date: Wed, 24 Aug 2022 02:59:58 -0300 Subject: [PATCH 22/27] bit of styling --- style.css | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/style.css b/style.css index 0e72f39..ae3f577 100644 --- a/style.css +++ b/style.css @@ -1,14 +1,16 @@ body { font-family: 'Chivo', sans-serif; font-weight: 400; + background-color: black; + color: white; } main { height: fit-content; } -body, .sidebar { - background-color: black; +.sidebar { + background-color: #111; color: white; } @@ -83,6 +85,38 @@ p > img { color: white; } +.sidebar { + padding-top: 0; +} + +.sidebar-toggle { + background: rgb(50,50,50,0.5) !important; + background: linear-gradient(60deg, rgba(50,50,50,0.5) 80px, rgba(0,0,0,0) 100%) !important; +} + +.search{ + padding: 12px !important; + border-bottom: 1px solid #777 !important; +} + +.sidebar hr{ + border-color: #666; + margin: 16px; +} + +.sidebar>h1{ + font-size: 1.6rem; +} + +input{ + background-color: #333; + color: white; +} + +input::placeholder { + color: #cccccc; +} + .anchor span { color: white; } From e2d4563da56387b472e42af16d04ed38c7e04ada Mon Sep 17 00:00:00 2001 From: Renzo Torrisi <56176668+ritchse@users.noreply.github.com> Date: Wed, 24 Aug 2022 03:08:30 -0300 Subject: [PATCH 23/27] script order --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 6626402..e70494d 100644 --- a/index.html +++ b/index.html @@ -66,9 +66,9 @@ + - From e03e50d383a23427256f0b4afded5f1f3f54150b Mon Sep 17 00:00:00 2001 From: Renzo Torrisi <56176668+ritchse@users.noreply.github.com> Date: Wed, 24 Aug 2022 03:13:03 -0300 Subject: [PATCH 24/27] script order --- index.html | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index e70494d..41d4136 100644 --- a/index.html +++ b/index.html @@ -64,16 +64,18 @@ crossorigin="anonymous" > - - - - - - + +
+ + + + + + From 2199d9dd68625da9d5e7dbdfa64c092323c86e3a Mon Sep 17 00:00:00 2001 From: Renzo Torrisi <56176668+ritchse@users.noreply.github.com> Date: Wed, 24 Aug 2022 03:33:18 -0300 Subject: [PATCH 25/27] fix intersect --- style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.css b/style.css index ae3f577..a9c0855 100644 --- a/style.css +++ b/style.css @@ -65,7 +65,7 @@ p > img { } .markdown-section{ - padding-bottom: calc(50vh - 360px - 75px + 5px); + padding-bottom: calc(50vh - 360px + 5px); } .markdown-section em { From b300cacfa54576fda85067509d960d86af92a351 Mon Sep 17 00:00:00 2001 From: Renzo Torrisi <56176668+ritchse@users.noreply.github.com> Date: Wed, 24 Aug 2022 03:58:33 -0300 Subject: [PATCH 26/27] update guides --- guides/advanced.md | 14 +++--- guides/audio.md | 6 +-- guides/external_sources.md | 2 +- guides/glsl.md | 1 + guides/javascript.md | 87 ++++++++++++++++++++++++++------------ 5 files changed, 73 insertions(+), 37 deletions(-) diff --git a/guides/advanced.md b/guides/advanced.md index d105a15..e18f583 100644 --- a/guides/advanced.md +++ b/guides/advanced.md @@ -29,7 +29,7 @@ gradient() #### Changing the global bpm for Arrays -To change how rapidly Hydra switches from element to element of any Array, you can change the `bpm` variable (meaning beats per minute) to any value you desire: +To change how rapidly Hydra switches from element to element of all Arrays, you can change the `bpm` variable (meaning beats per minute) to any value you desire: ```hydra bpm = 138 // change me ! @@ -41,13 +41,15 @@ gradient() The default value for `bpm` is 30. +When livecoding visuals at the same time that music is playing, it can be useful to have a tapping metronome opened to keep track of the BPM being played and set this variable as such. + #### Changing the speed of a specific Array -Hydra adds a couple of methods to all arrays to be used inside Hydra. `.fast` will control the speed of the array from which it is called. It receives a Number as argument, by which the global speed will be multiplied. So calling `.fast(1)` on an Array is the same as nothing. Higher values will generate faster switching, while lower than 1 values will be slower. +Hydra adds a couple of methods to all Arrays to be used inside Hydra. `.fast` will control the speed at which Hydra takes elements from the Array. It receives a Number as argument, by which the global speed will be multiplied. So calling `.fast(1)` on an Array is the same as nothing. Higher values will generate faster switching, while lower than 1 values will be slower. ```hydra bpm = 45 -osc([20,30,50,60],.1,[0,1.5].fast(1.5)) +osc([20,30,50,60],.1,[0,1.5].fast(1.5)) // 50% faster //.rotate([-.2,0,.2].fast(1)) // try different speeds for each array .out() ``` @@ -118,7 +120,7 @@ The following are the available easing functions: * easeInOutQuint * sin: sinusoidal shape -#### Note on storing Arrays on variables +#### Note on storing Arrays on variables / functions Storing an Array in a variable can lead to some trouble as soon as you apply some of the just-mentioned functions to it. Since Arrays are Objects, each time you call your variable, you'll be calling the same Object. If you apply some speed via `.fast` or smoothness via `.smooth` somewhere in your patch, and then use the same variable, all the following uses of the Array will also have these effects applied to them. For example @@ -321,7 +323,7 @@ Still, always keep in mind while using iteration, that the more effects and iter #### .forEach, .map and .reduce -Those familiar with more array focused programming languages such as Python or Haskell, or more functional structures even inside JavaScript, may be used to iterating using the `forEach`, `map` and/or `reduce` structures. Where given an Array, we use each value to alter something or to reduce the entire array into a desired result. +Those familiar with more array focused programming languages such as Python or Haskell, or more functional structures even inside JavaScript, may be used to iterating using the `forEach`, `map` and/or `reduce` structures. Where given an Array, we use each value to alter something or to reduce the entire Array into a desired result. Practically anything done with these functions can be done using `for` loops, so if you are new to these or you just don't like how they look, then there really is no need for you to learn these, even if you're super interested in iteration. ##### .forEach @@ -358,7 +360,7 @@ result().out() ##### .map -Haters of state (non-political) will prefer `.map` any day over `.forEach`. Looking at the example for `.forEach`, we see were creating a texture and adding it to an accumulator _for each_ element in the array. We can separate the texture generating part of the code and the blending part using `.map` to get an array of textures and `.reduce` to blend them: +Haters of state (non-political) will prefer `.map` any day over `.forEach`. Looking at the example for `.forEach`, we see were creating a texture and adding it to an accumulator _for each_ element in the Array. We can separate the texture generating part of the code and the blending part using `.map` to get an array of textures and `.reduce` to blend them: ```hydra text = "Hydra >:]" diff --git a/guides/audio.md b/guides/audio.md index d49a9ca..cc1825b 100644 --- a/guides/audio.md +++ b/guides/audio.md @@ -10,7 +10,7 @@ In order to achieve audio reactivity, Hydra makes use of a JavaScript library ca Sound travels through air as a wave, that's fairly common knowledge. This basically means that sound is nothing more than air pressure going up and down through time very fast in weird ways. But we don't experience sound simply as something that goes on and off like a light flickering, many of the sounds we are used to have some sort of frequency or repetition that we interpret as higher or lower pitch. When we listen to a song we can easily differentiate the bass guitar from the singer even if both are playing at the same time. If there are two vocals being sung at the same time, even if by the same person, we can differentiate them because of how high or low they are (also because of timbre, but that doesn't matter at all right now). When we talk about the audio spectrum, we are talking about the many frequencies a sound can cover and we humans can hear. -What a fast fourier transform does is basically hear some ongoing sound and interpret how present the sound is on different parts of this spectrum. For example, if we separate the audio spectrum in 3 equal parts and play a drumkit, the bass drum will have more presence on the lower side of the spectrum, while a hi-hat will surely have most of its presence on the higher third part of the spectrum. +What a fast fourier transform does is basically hear some ongoing sound and interpret how present the sound is on different parts of this spectrum. For example, if we separate the audio spectrum in 3 equal parts and play a drum-kit, the bass drum will have more presence on the lower side of the spectrum, while a hi-hat will surely have most of its presence on the higher third part of the spectrum. ### a.show() & a.hide() @@ -34,7 +34,7 @@ osc(20,.1,2) See how if you make a deep "O" sound into the mic, the rotation will be strong and the saturation won't be affected as much. Also try to make a high "S" sound, you'll see the exact opposite. -Note how we use brackets to call an element in an array, and that we start counting from 0. +Note how we use brackets to call an element in an Array, and that we start counting from 0. #### a.bins & a.prevBins @@ -61,7 +61,7 @@ Hydra also has a simple beat detection algorithm. You can change this function t ### Reacting to music -As we've seen, Hydra takes your microphone as an input, not your desktop audio. Those interested in using a music player or a DAW's output as an audio input will have to delve into virtual audio routing. +As we've seen, Hydra takes your microphone as an input, not your desktop audio. Those interested in using a music player or a DAW's output as an audio input will have to delve into virtual audio routing. Users with physical sound interfaces with multiple inputs and outputs might prefer physically routing an output to an input and set that input as the default microphone on Chrome. --- diff --git a/guides/external_sources.md b/guides/external_sources.md index f603bb9..4a469bc 100644 --- a/guides/external_sources.md +++ b/guides/external_sources.md @@ -71,7 +71,7 @@ Hydra (the editor) also has built-in streaming. You can stream the output of you On your Hydra editor, you can find a pre-defined object called `pb` (as in patch-bay). This object basically represents the connection of your Hydra editor instance to all others hosted on the same server. When you want to share your stream to someone else you'll have to give your Hydra session a name. Do this using the `pb.setName()` function and by passing in some string as the name. For example: `pb.setName('myverycoolsession')`. If you want someone else to stream to you, ask them to set a name as such and share it with you. -You can see online sessions using the function `pb.list()`, which will return an array of names. +You can see online sessions using the function `pb.list()`, which will return an Array of names. ### Starting to stream diff --git a/guides/glsl.md b/guides/glsl.md index d71280e..6cc6798 100644 --- a/guides/glsl.md +++ b/guides/glsl.md @@ -1,4 +1,5 @@ # GLSL Guide +--- ## Using custom GLSL functions diff --git a/guides/javascript.md b/guides/javascript.md index 255f909..670ad89 100644 --- a/guides/javascript.md +++ b/guides/javascript.md @@ -44,7 +44,7 @@ noise(2,.5) Variables are spaces of memory in your computer that you reserve to store some value. Each variable you use will have a unique symbolic name. This definition may sound complicated, but you'll see it's really as intuitive as it can be. You may remember variables from mathematics being letters that represent some sort of number. This is precisely the same, you just choose some name and assign some number (or other type of information) to it. -```javascript +```hydra freq = 50 // change this value and see what happens osc(freq) .diff(osc(freq).rotate()) @@ -61,7 +61,7 @@ In the previous example, `freq` is the name of the variable and `50` is its valu Variable names can't start with numbers, they start with letters and it's conventional in JavaScript to start with a lowercase. When the name of your variable is more than one word, it's also conventional to write them as such: -```javascript +```hydra feedbackIntensity = .3 src(o0) .colorama(feedbackIntensity/10) @@ -74,7 +74,7 @@ However this is just a convention, you may find other ways of naming your variab ### Global variables -When you declare a variable in Hydra, it declares it for you on the global scope. You can imagine a scope as a piece of code that works on its own and has its own variables. However, the global scope is basically a bunch of variables and functions that can be accessed from anywhere (functions such as `osc()` are declared in the global scope so that you can use them by just calling them, no matter where, for example). +When you declare a variable in Hydra, it declares it for you on the global scope. You can imagine a [scope](https://developer.mozilla.org/docs/Glossary/Scope) as a piece of code that works on its own and has its own variables. However, the global scope is basically a bunch of variables and functions that can be accessed from anywhere (functions such as `osc()` are declared in the global scope so that you can use them by just calling them, no matter where, for example). We can make it explicit that we want something on the global scope. In JavaScript, since it's made to run on a browser, we do this by declaring variables on the `window` object (what is an object, you can find out below), which represents the browser's window. ```javascript @@ -105,20 +105,31 @@ This knowledge will come in handy if you start coding functions for example, sin ## Arrays -Arrays are basically lists of values. Instead of declaring 100 variables to represent different values of the same concept you can just use a list of values. The key thing is these values are related, they will serve the same purpose somewhere in our code. If they are not related, using a list isn't really useful, we'll be just confusing ourselves thinking about where in the list did we put this or that other value. Here's an example of an array: +[Arrays](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array) are basically lists of values. Instead of declaring 100 variables to represent different values of the same concept you can just use a list of values. The key thing is these values are related, they will serve the same purpose somewhere in our code. If they are not related, using a list isn't really useful, we'll be just confusing ourselves thinking about where in the list did we put this or that other value. Here's an example of an Array: -```javascript +```hydra rots = [0,.5,1.2,2.2,3] // this is how we declare an array osc().rotate(rots[0]) // this is how we call an element from an array .diff(osc().rotate(rots[2])) .out() ``` -The example above isn't that useful in a Hydra context, but we hope it illustrates the basics of how an array is created and used. -Arrays in JavaScript (and in most programming languages) start counting their elements from 0 and not from 1. So if you want the first element of the `rots` array, you need to call `array[0]` instead of `array[1]`! Same goes for every element. If you want the third element call `array[2]`, and so on. Remember that nth element = array[n-1] +The example above isn't that useful in a Hydra context, but we hope it illustrates the basics of how an Array is created and used. +Arrays in JavaScript (and in most programming languages) start counting their elements from 0 and not from 1. So if you want the first element of the `rots` Array, you need to call `array[0]` instead of `array[1]`! Same goes for every element. If you want the third element call `array[2]`, and so on. Remember that `nth element = array[n-1]` -Arrays in Hydra can be used to create dynamic inputs. +### Arrays as sequences + +Arrays in Hydra can be used as inputs. Hydra takes the list of values and makes a sequence out of them: + +```hydra +rots = [0,.5,1.2,2.2,3] +osc().rotate([-.5,.5]) + .diff(osc().rotate(rots)) + .out() +``` + +You can learn more about dynamic inputs [here](/guides/advanced#dynamic-inputs). --- @@ -161,7 +172,8 @@ sum = (a,b) => a+b ### Functions that return a texture -```javascript +```hydra +// works properly on the editor circle = () => shape(64,.4,.1).scale(1,innerHeight/innerWidth) circle() .out() @@ -172,7 +184,8 @@ Another reason we would use a function in this example is that if we want to add Let's see how we could make the circle function more useful by adding parameters: -```javascript +```hydra +// works properly on the editor circle = (size,blur=.1)=> shape(64,size,blur) .scale(1,()=>innerHeight/innerWidth) circle(.4) @@ -181,51 +194,70 @@ circle(.4) ``` Now, each time we call the circle function we can specify a size and blur. We can also omit the blur and the function will use the default value specified next to it. -We also changed the scaling to an arrow function, which you may find surprising if you haven't seen it before. When you use a function as an argument, Hydra will evaluate that function every time it renders a frame and use the return of that function in the rendering of that frame. In other words, functions can be used as dynamic inputs. +We also changed the scaling to an arrow function, which you may find surprising if you haven't seen it before. When you use a function as an argument, Hydra will evaluate that function every time it renders a frame and use the return of that function in the rendering of that frame. In other words, functions can be used as [dynamic inputs](/guides/advanced#functions). ### Using declared functions as inputs As we just mentioned, we can use arrow functions inside the arguments of a given source or transform for it to react in real time. If you have many arguments using the same arrow function, you may want to declare it and reuse its name: -```javascript +```hydra scaling = () => .9+(Math.sin(time*2)/3) noise(3) - .scale(scaling) - .layer(src(o0).scale(scaling).mask(shape())) + .scale(scaling) + .layer( + src(o0) + .scale(scaling) + .mask(shape()) + ) .out() ``` #### Calling declared functions from other functions -Sometimes you want to reuse a function but have something change about it. For example, maybe we want to make the scaling negative for the feedback in the last example. But calling `-scaling` doesn't make sense, at least to JavaScript, since the negative of a function doesn't exist. But the negative of its return might: +Sometimes you want to reuse a function but have something change about it. For example, maybe we want to make the scaling negative for the feedback in the last example. But calling `-scaling` doesn't make sense, at least to JavaScript, since the negative of a function doesn't exist. But the negative of its return does: -```javascript +```hydra scaling = () => .9+(Math.sin(time*2)/3) noise(3) - .scale(scaling) - .layer(src(o0).scale(()=>-scaling()).mask(shape())) + .scale(scaling) + .layer( + src(o0) + .scale(()=>-scaling()) + .mask(shape()) + ) .out() ``` -You'll also come across this if your function has an input. For example: +##### Note on functions with parameters + +You'll also come across this if your function has parameters. For example: + ```javascript scaling = (multiplier)=> (.9+(Math.sin(time*2)/3))*multiplier ``` + Doing `.scale(scaling)` doesn't make sense anymore, since you aren't giving it its necessary input. And if you try to do `.scale(scaling(-1))`, Hydra will evaluate the function once and use its return as the input to scale, instead of using a function which is what we want for the visual to react to the changes in time. The solution is, again, a function that calls your function, such as `.scale(()=>scaling(-1))`. If for some reason you hate arrow functions, you could also try [binding it](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) doing `.scale(scaling.bind(0,1))`. ### Higher-order functions Higher order functions just means functions that take other functions as arguments. These are useful when you want to make u new functions which take behavior from other functions. As an example, let's visualize applying a sine function (with some tweaks) to itself: -```javascript +```hydra fps=40 twice = (func,...args) => func(func(...args)) myFunc = (x)=> Math.sin(x*2)*2 + src(o0) .luma(.02) .mult(solid(),.02) - .add(shape(32,.02).scroll(0,()=>0.25+myFunc(time)/50,-.2)) - .add(shape(32,.02).scroll(0,()=>-.25+twice(myFunc,time)/50,-.2)) + .add( + shape(32,.02) + .scroll(0, ()=>0.25+myFunc(time)/50, -.2) + ) + .add( + shape(32,.02) + .scroll(0, ()=>-.25+twice(myFunc,time)/50, -.2) + ) .out() ``` @@ -241,7 +273,7 @@ For example, if you ever use Hydra on instance mode, what you'll come across is Let's see an example of how to declare and use an object with some properties: -```javascript +```hydra coords = {x: 0.25, y: 0.25} // this is how we declare an object with some properties shape(8,.1) .scroll(coords.x,coords.y) // this is how we call properties @@ -250,7 +282,7 @@ shape(8,.1) And now let's add a method: -```javascript +```hydra coords = { x: 0.25, y: 0.25, @@ -290,9 +322,10 @@ shape(4,.4).scale(1,screenRatio) .out() ``` -There's also the less used `screenX` and `screenY` which will tell you the position of the window relative to the full screen. Try to resize the window and move it around with the following example: +There's also the less used `screenX` and `screenY` which will tell you the position of the window relative to the full screen. +Try to move your browser's window with the following example: -```javascript +```hydra osc() .rotate(()=>20+(screenX/300)) .out() @@ -309,6 +342,6 @@ You can see the full list of functions and variables in the Math object [clickin One of the most useful predefined variables that the Math API has is the value of pi (well, an approximation considering that pi has infinite decimals). Many Hydra functions take radians as arguments which you may know are usually represented using multiples of pi. For example, if you want to rotate a texture exactly half a pi (90 degrees), you can do it as such: -``` +```hydra osc().rotate(Math.PI/2).out() ``` From bc098dc48533b3beaf736b2a145567903bdead27 Mon Sep 17 00:00:00 2001 From: Renzo Torrisi <56176668+ritchse@users.noreply.github.com> Date: Thu, 25 Aug 2022 01:54:21 -0300 Subject: [PATCH 27/27] update interactivity guide --- guides/interactivity.md | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/guides/interactivity.md b/guides/interactivity.md index c1e7f19..3037bea 100644 --- a/guides/interactivity.md +++ b/guides/interactivity.md @@ -1,19 +1,25 @@ # Interactivity Guide --- +#### Note + +All of the examples using mouse position to move stuff on the canvas won't work well here, since the canvas doesn't occupy the full size of the screen as in the editor. Take this into account when we use `mouse`, that the positions are relative to the full webpage and not the canvas. This also means that as you scroll down this guide the `y` value will get higher and higher. + +--- + ## Using the mouse You can have your visuals react to the position of your mouse (or finger, in touch devices). Hydra has an object called `mouse` which stores and keeps track of the position of your mouse on the webpage. ### mouse.x & mouse.y -```javascript +```hydra gradient() .hue(()=>mouse.x/3000) - .scale(1,1,()=>mouse.y/100) + .scale(1,1,()=>mouse.y/1000) .out() ``` - +| You can refer to the pixel position of your mouse by calling `mouse.x` and `mouse.y`, each one corresponding to the horizontal and vertical coordinates respectively. When we say 'pixel position', this means that the values you'll find stored in both x and y are represented in pixels. So for `mouse.x`, this means the amount of pixels from the left edge of your window to the position of your mouse. For `mouse.y`, this means the amount of pixels between the top end of your screen and the position of your mouse. Many times it will be most useful to use values relative to the size of the screen. And also to have values that exist between ranges more reasonable to the hydra functions you're using. For example [-0.5; 0.5] for scrollX and scrollY, [0; 2pi] for rotation, or [0; 1] for general purposes. @@ -24,7 +30,7 @@ On Hydra, most values used are pretty small. So it will be way more useful to ha #### Getting values from 0 to 1 -```javascript +```hydra x = () => mouse.x/innerWidth // 0→1 y = () => mouse.y/innerHeight // 0→1 osc() @@ -41,7 +47,7 @@ On Hydra, things are placed between 0.5 and -0.5 (left to right, top to bottom). #### Getting values from 0 to ±0.5 from the center -```javascript +```hydra x = () => (-mouse.x/innerWidth)+.5 // 0.5→-0.5 y = () => (-mouse.y/innerHeight)+.5 // 0.5→-0.5 solid(255) @@ -77,12 +83,14 @@ One of the key things you need to take into account when working with mouse and #### Toggle transforms on click -```javascript +```hydra toggle = 0 onclick = () => { toggle = toggle ? 0 : 1 } osc(30,.2,1.5) .rotate(()=>toggle*Math.PI/2) .out() + +onpointerup = null // ignore this for now ``` Note that in JavaScript, you can pass any value as a boolean and JavaScript will try to interpret it in some way (search for truthy and falsy if you want to learn more). With numbers, `0` will act as `false` and any other number will act as `true`. We can make use of this to generate our toggling action. Fans of bitwise operators might implement the toggle as `toggle = toggle ^ 1`, where `^` means XOR. @@ -90,7 +98,7 @@ Fans of bitwise operators might implement the toggle as `toggle = toggle ^ 1`, w ##### Pointer version We can simplify a click to be simply when any pointer is released (or pressed, whichever you prefer): -```javascript +```hydra onclick = null // getting rid of the conflicting click event // toggle = 0 @@ -103,7 +111,7 @@ Note that this example won't work if run after the previous one. Since both even #### Activate transforms while clicking -```javascript +```hydra press = 0 onpointerdown = () => { press = 1 } onpointerup = () => { press = 0 } @@ -116,7 +124,7 @@ Note how we make use of the ternary operator again to get arbitrarily different #### Count the amount of clicks -```javascript +```hydra clicks = 1 onpointerup = () => { clicks++ } noise(()=>clicks) @@ -141,7 +149,7 @@ osc(15,.1,()=>pressedTime) Ok, now that we see how tedious it can be to use intervals, let's see how we can do this in a more Hydrated way. As we just said, we could try checking if the pointer is being pressed each time Hydra renders a frame. Coincidentally, the arrows functions we love to use as arguments for our functions, are checked by Hydra every frame! We could try writing the action that updates our `pressedTime` inside one: -```javascript +```hydra press = false onpointerdown = () => { press = true } onpointerup = () => { press = false } @@ -155,7 +163,7 @@ There's another way of making Hydra run some functionality each frame, which is #### Generating new patches on clicks Since outputting is simply a function call as any other. We can also try to evaluate patches when an event is triggered: -```javascript +```hydra textures = [noise(),osc(),osc(50).kaleid(),voronoi(),noise().thresh(0)] onpointerdown = () => { osc(40,.05,Math.random()*2) @@ -176,8 +184,8 @@ To use keyboard input, we can use `KeyboardEvent`'s such as `keydown`, `keyup`. #### Moving stuff with the arrow keys -Here either add or subtract one from our x and y values depending on which arrow key the user presses. Each representing one of the 2 dimensional axis'. -```javascript +Here either add or subtract one from our x and y values depending on which arrow key the user presses. Each representing one of the 2 dimensional axis. +```hydra x = 0; y = 0; onkeyup = (event) => { switch(event.key){ @@ -192,13 +200,15 @@ noise(6) .scroll(()=>x/10,()=>y/10) .sub(shape(4,.02).scroll(()=>x/10,()=>y/10),2) .out() + +onpointerdown = null // getting rid of the previous listener ``` #### Example: Typing shapes Here we use the `keyCode` of the last pressed key to change the shape being shown. This is a modification of ax example from the page about iteration. There we used ASCII codes to automatically generate shapes. However, ASCII and keyCodes of given letters are different! -```javascript +```hydra key = 25 onkeydown = (e) => { key = e.keyCode } src(o0) @@ -211,4 +221,7 @@ src(o0) .rotate(()=>Math.PI*key/100) ) .out() + +// type anything here: +// ``` \ No newline at end of file