Hydra looks better by default

This commit is contained in:
2023-11-26 02:24:47 +01:00
parent eb8ef879e7
commit 626a8be77c
5 changed files with 57 additions and 38 deletions

View File

@ -128,7 +128,7 @@
</header> </header>
<div id="documentation" class="hidden"> <div id="documentation" class="hidden">
<div id="documentation-page" class="flex flex-row"> <div id="documentation-page" class="flex flex-row bg-transparent">
<aside class="w-1/8 flex-shrink-0 h-screen overflow-y-auto p-1 lg:p-6 bg-neutral-900 text-white"> <aside class="w-1/8 flex-shrink-0 h-screen overflow-y-auto p-1 lg:p-6 bg-neutral-900 text-white">
<nav class="text-xl sm:text-sm overflow-y-scroll mb-24"> <nav class="text-xl sm:text-sm overflow-y-scroll mb-24">
<details class="" open=true> <details class="" open=true>
@ -217,7 +217,7 @@
</details> </details>
</nav> </nav>
</aside> </aside>
<div id="documentation-content" class="w-full flex-grow-1 h-screen overflow-y-scroll lg:px-12 mx-2 my-2 break-words pb-32"> <div id="documentation-content" class="w-full flex-grow-1 h-screen overflow-y-scroll lg:px-12 mx-2 my-2 break-words pb-32 bg-transparent"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -86,6 +86,7 @@ export class UserAPI {
private printTimeoutID: number = 0; private printTimeoutID: number = 0;
public MidiConnection: MidiConnection; public MidiConnection: MidiConnection;
public scale_aid: string | number | undefined = undefined; public scale_aid: string | number | undefined = undefined;
public hydra: any;
load: samples; load: samples;
constructor(public app: Editor) { constructor(public app: Editor) {

View File

@ -32,13 +32,13 @@ export const saveBeforeExit = (app: Editor): null => {
export const installWindowBehaviors = ( export const installWindowBehaviors = (
app: Editor, app: Editor,
window: Window, window: Window,
preventMultipleTabs: boolean = false, preventMultipleTabs: boolean = false
) => { ) => {
window.addEventListener("resize", () => window.addEventListener("resize", () =>
handleResize(app.interface.scope as HTMLCanvasElement), handleResize(app.interface.scope as HTMLCanvasElement)
); );
window.addEventListener("resize", () => window.addEventListener("resize", () =>
handleResize(app.interface.feedback as HTMLCanvasElement), handleResize(app.interface.feedback as HTMLCanvasElement)
); );
window.addEventListener("beforeunload", (event) => { window.addEventListener("beforeunload", (event) => {
event.preventDefault(); event.preventDefault();
@ -61,11 +61,11 @@ export const installWindowBehaviors = (
if (e.key == "page_available") { if (e.key == "page_available") {
document.getElementById("all")!.classList.add("invisible"); document.getElementById("all")!.classList.add("invisible");
alert( alert(
"Topos is already opened in another tab. Close this tab now to prevent data loss.", "Topos is already opened in another tab. Close this tab now to prevent data loss."
); );
} }
}, },
false, false
); );
} }
}; };

View File

@ -7,7 +7,7 @@ export const bonus = (application: Editor): string => {
return ` return `
# Bonus features # Bonus features
Some features are here "just for fun" or "just because I can". They are not very interesting per se but are still available nonetheless. They mostly gravitate towards manipulating visuals or patterning other multimedia formats. Some features have been included as a bonus. These features are often about patterning over things that are not directly related to sound: pictures, video, etc.
## Hydra Visual Live Coding ## Hydra Visual Live Coding
@ -15,19 +15,19 @@ Some features are here "just for fun" or "just because I can". They are not very
<warning>⚠️ This feature can generate flashing images that could trigger photosensitivity or epileptic seizures. ⚠️ </warning> <warning>⚠️ This feature can generate flashing images that could trigger photosensitivity or epileptic seizures. ⚠️ </warning>
</div> </div>
[Hydra](https://hydra.ojack.xyz/?sketch_id=mahalia_1) is a popular live-codable video synthesizer developed by [Olivia Jack](https://ojack.xyz/) and other contributors. It follows the metaphor of analog synthesizer patching to allow its user to create complex live visuals from a web browser window. Being very easy to use, extremely powerful and also very rewarding to use, Hydra has become a popular choice for adding visuals into a live code performance. Topos provides a simple way to integrate Hydra into a live coding session and to blend it with regular Topos code. [Hydra](https://hydra.ojack.xyz/?sketch_id=mahalia_1) is a popular live-codable video synthesizer developed by [Olivia Jack](https://ojack.xyz/) and other contributors. It follows an analog synthesizer patching metaphor to encourage live coding complex shaders. Being very easy to use, extremely powerful and also very rewarding to use, Hydra has become a popular choice for adding visuals into a live code performance.
${makeExample( ${makeExample(
"Hydra integration", "Hydra integration",
`beat(4) :: app.hydra.osc(3, 0.5, 2).out()`, `beat(4) :: hydra.osc(3, 0.5, 2).out()`,
true, true
)} )}
You may feel like it's doing nothing! Press ${key_shortcut( Close the documentation to see the effect: ${key_shortcut(
"Ctrl+D", "Ctrl+D"
)} to close the documentation. **Boom, all shiny!** )}! **Boom, all shiny!**
Be careful not to call <ic>app.hydra</ic> too often as it can impact performances. You can use any rhythmical function like <ic>mod()</ic> function to limit the number of function calls. You can write any Topos code like <ic>[1,2,3].beat()</ic> to bring some life and movement in your Hydra sketches. Be careful not to call <ic>hydra</ic> too often as it can impact performances. You can use any rhythmical function like <ic>beat()</ic> function to limit the number of function calls. You can write any Topos code like <ic>[1,2,3].beat()</ic> to bring some life and movement in your Hydra sketches.
Stopping **Hydra** is simple: Stopping **Hydra** is simple:
@ -35,16 +35,35 @@ ${makeExample(
"Stopping Hydra", "Stopping Hydra",
` `
beat(4) :: stop_hydra() // this one beat(4) :: stop_hydra() // this one
beat(4) :: app.hydra.hush() // or this one beat(4) :: hydra.hush() // or this one
`, `,
true, true
)} )}
I won't teach you how to play with Hydra. You can find some great resources on the [Hydra website](https://hydra.ojack.xyz/):
### Changing the resolution
You can change Hydra resolution using this simple method:
${makeExample(
"Changing Hydra resolution",
`hydra.setResolution(1024, 768)`,
true
)}
### Documentation
I won't teach Hydra. You can find some great resources directly on the [Hydra website](https://hydra.ojack.xyz/):
- [Hydra interactive documentation](https://hydra.ojack.xyz/docs/) - [Hydra interactive documentation](https://hydra.ojack.xyz/docs/)
- [List of Hydra Functions](https://hydra.ojack.xyz/api/) - [List of Hydra Functions](https://hydra.ojack.xyz/api/)
- [Source code on GitHub](https://github.com/hydra-synth/hydra) - [Source code on GitHub](https://github.com/hydra-synth/hydra)
### The Hydra namespace
In comparison with the basic Hydra editor, please note that you have to prefix all Hydra functions with <ic>hydra.</ic> to avoid conflicts with Topos functions. For example, <ic>osc()</ic> becomes <ic>hydra.osc()</ic>.
${makeExample("Hydra namespace", `hydra.voronoi(20).out()`, true)}
## GIF player ## GIF player
Topos embeds a small <ic>.gif</ic> picture player with a small API. GIFs are automatically fading out after the given duration. Look at the following example: Topos embeds a small <ic>.gif</ic> picture player with a small API. GIFs are automatically fading out after the given duration. Look at the following example:
@ -63,7 +82,7 @@ beat(0.25)::gif({
posX: ir(1,1200), // CSS Horizontal Position posX: ir(1,1200), // CSS Horizontal Position
posY: ir(1, 800), // CSS Vertical Position posY: ir(1, 800), // CSS Vertical Position
`, `,
true, true
)} )}
`; `;
}; };

View File

@ -222,7 +222,7 @@ export class Editor {
updateKnownUniversesView = () => { updateKnownUniversesView = () => {
let itemTemplate = document.getElementById( let itemTemplate = document.getElementById(
"ui-known-universe-item-template", "ui-known-universe-item-template"
) as HTMLTemplateElement; ) as HTMLTemplateElement;
if (!itemTemplate) { if (!itemTemplate) {
console.warn("Missing template #ui-known-universe-item-template"); console.warn("Missing template #ui-known-universe-item-template");
@ -250,10 +250,10 @@ export class Editor {
item item
.querySelector(".delete-universe") .querySelector(".delete-universe")
?.addEventListener("click", () => ?.addEventListener("click", () =>
api._deleteUniverseFromInterface(it), api._deleteUniverseFromInterface(it)
); );
return item; return item;
}), })
); );
existing_universes.innerHTML = ""; existing_universes.innerHTML = "";
@ -334,7 +334,7 @@ export class Editor {
this.view.dispatch({ this.view.dispatch({
effects: this.chosenLanguage.reconfigure( effects: this.chosenLanguage.reconfigure(
this.editor_mode == "notes" ? [markdown()] : [javascript()], this.editor_mode == "notes" ? [markdown()] : [javascript()]
), ),
}); });
@ -343,7 +343,7 @@ export class Editor {
setButtonHighlighting( setButtonHighlighting(
button: "play" | "pause" | "stop" | "clear", button: "play" | "pause" | "stop" | "clear",
highlight: boolean, highlight: boolean
) { ) {
document.getElementById("play-label")!.textContent = document.getElementById("play-label")!.textContent =
button !== "pause" ? "Pause" : "Play"; button !== "pause" ? "Pause" : "Play";
@ -391,7 +391,7 @@ export class Editor {
// All other buttons must lose the highlighting // All other buttons must lose the highlighting
document document
.querySelectorAll( .querySelectorAll(
possible_selectors.filter((_, index) => index != selector).join(","), possible_selectors.filter((_, index) => index != selector).join(",")
) )
.forEach((button) => { .forEach((button) => {
button.children[0].classList.remove("animate-pulse"); button.children[0].classList.remove("animate-pulse");
@ -437,28 +437,28 @@ export class Editor {
flashBackground(color: string, duration: number): void { flashBackground(color: string, duration: number): void {
const domElement = this.view.dom; const domElement = this.view.dom;
const gutters = domElement.getElementsByClassName( const gutters = domElement.getElementsByClassName(
"cm-gutter", "cm-gutter"
) as HTMLCollectionOf<HTMLElement>; ) as HTMLCollectionOf<HTMLElement>;
domElement.classList.add("fluid-bg-transition"); domElement.classList.add("fluid-bg-transition");
Array.from(gutters).forEach((gutter) => Array.from(gutters).forEach((gutter) =>
gutter.classList.add("fluid-bg-transition"), gutter.classList.add("fluid-bg-transition")
); );
domElement.style.backgroundColor = color; domElement.style.backgroundColor = color;
Array.from(gutters).forEach( Array.from(gutters).forEach(
(gutter) => (gutter.style.backgroundColor = color), (gutter) => (gutter.style.backgroundColor = color)
); );
setTimeout(() => { setTimeout(() => {
domElement.style.backgroundColor = ""; domElement.style.backgroundColor = "";
Array.from(gutters).forEach( Array.from(gutters).forEach(
(gutter) => (gutter.style.backgroundColor = ""), (gutter) => (gutter.style.backgroundColor = "")
); );
domElement.classList.remove("fluid-bg-transition"); domElement.classList.remove("fluid-bg-transition");
Array.from(gutters).forEach((gutter) => Array.from(gutters).forEach((gutter) =>
gutter.classList.remove("fluid-bg-transition"), gutter.classList.remove("fluid-bg-transition")
); );
}, duration); }, duration);
} }
@ -466,7 +466,7 @@ export class Editor {
private initializeElements(): void { private initializeElements(): void {
for (const [key, value] of Object.entries(singleElements)) { for (const [key, value] of Object.entries(singleElements)) {
this.interface[key] = document.getElementById( this.interface[key] = document.getElementById(
value, value
) as ElementMap[keyof ElementMap]; ) as ElementMap[keyof ElementMap];
} }
} }
@ -474,7 +474,7 @@ export class Editor {
private initializeButtonGroups(): void { private initializeButtonGroups(): void {
for (const [key, ids] of Object.entries(buttonGroups)) { for (const [key, ids] of Object.entries(buttonGroups)) {
this.buttonElements[key] = ids.map( this.buttonElements[key] = ids.map(
(id) => document.getElementById(id) as HTMLButtonElement, (id) => document.getElementById(id) as HTMLButtonElement
); );
} }
} }
@ -501,20 +501,19 @@ export class Editor {
enableStreamCapture: false, enableStreamCapture: false,
}); });
this.hydra = this.hydra_backend.synth; this.hydra = this.hydra_backend.synth;
(globalThis as any).hydra = this.hydra;
this.hydra.setResolution(1024, 768);
} }
private setCanvas(canvas: HTMLCanvasElement): void { private setCanvas(canvas: HTMLCanvasElement): void {
if (!canvas) return; if (!canvas) return;
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
const dpr = window.devicePixelRatio || 1; const dpr = window.devicePixelRatio || 1;
// Assuming the canvas takes up the whole window // Assuming the canvas takes up the whole window
canvas.width = window.innerWidth * dpr * 0.25; canvas.width = window.innerWidth * dpr;
canvas.height = window.innerHeight * dpr * 0.25; canvas.height = window.innerHeight * dpr;
if (ctx) { if (ctx) {
ctx.scale(dpr * 0.5, dpr * 0.5); ctx.scale(dpr, dpr);
} }
} }
} }