From cad9fdbb40223447531adf3f590ba811f5ed4b43 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Fri, 15 Dec 2023 22:13:22 +0200 Subject: [PATCH] Added some drawing methods --- index.html | 1 + src/API.ts | 116 ++++++++++++++++++ src/DomElements.ts | 1 + src/Utils/Generic.ts | 9 ++ src/WindowBehavior.ts | 3 + src/classes/AbstractEvents.ts | 10 ++ src/classes/MidiEvent.ts | 5 +- src/classes/SoundEvent.ts | 11 +- src/documentation/patterns/generators.ts | 37 ++++++ .../patterns/ziffers/ziffers_basics.ts | 4 +- src/main.ts | 1 + 11 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 src/documentation/patterns/generators.ts diff --git a/index.html b/index.html index 0449411..c890349 100644 --- a/index.html +++ b/index.html @@ -530,6 +530,7 @@ + diff --git a/src/API.ts b/src/API.ts index 3e36155..0e38307 100644 --- a/src/API.ts +++ b/src/API.ts @@ -2186,6 +2186,122 @@ export class UserAPI { }, real_duration * 1000); }; + // ============================================================= + // Canvas Functions + // ============================================================= + + public clear = (): void => { + /** + * Clears the canvas after a given timeout. + * @param timeout - The timeout in seconds + */ + const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; + const ctx = canvas.getContext("2d")!; + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + + public circle = ( + x: number, + y: number, + radius: number, + fillStyle: string, + ): void => { + const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; + const ctx = canvas.getContext("2d")!; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, 2 * Math.PI); + ctx.fillStyle = fillStyle; + ctx.fill(); + }; + + public triangular = ( + x: number, + y: number, + radius: number, + fillStyle: string, + rotate: number + ): void => { + const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; + const ctx = canvas.getContext("2d")!; + ctx.save(); + ctx.translate(x, y); + ctx.rotate((rotate * Math.PI) / 180); + ctx.beginPath(); + ctx.moveTo(0, -radius); + ctx.lineTo(radius, radius); + ctx.lineTo(-radius, radius); + ctx.closePath(); + ctx.fillStyle = fillStyle; + ctx.fill(); + ctx.restore(); + } + + public star = ( + x: number, + y: number, + radius: number, + points: number = 5, + fillStyle: string = "white", + outerRadius: number = 1.0, + rotate: number = 0, + ): void => { + const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; + if(points<1) return this.circle(x, y, radius+outerRadius, fillStyle); + if(points==1) return this.triangular(x, y, radius, fillStyle, 0); + const ctx = canvas.getContext("2d")!; + ctx.save(); + ctx.translate(x, y); + ctx.rotate((rotate * Math.PI) / 180); + ctx.beginPath(); + ctx.moveTo(0, -radius); + for (let i = 0; i < points; i++) { + ctx.rotate(Math.PI / points); + ctx.lineTo(0, -(radius * outerRadius)); + ctx.rotate(Math.PI / points); + ctx.lineTo(0, -radius); + } + ctx.closePath(); + ctx.fillStyle = fillStyle; + ctx.fill(); + ctx.restore(); + }; + + public stroke = ( + x1: number, + y1: number, + x2: number, + y2: number, + fillStyle: string, + width: number = 1, + ): void => { + const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; + const ctx = canvas.getContext("2d")!; + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.strokeStyle = fillStyle; + ctx.lineWidth = width; + ctx.stroke(); + }; + + public rectangle = ( + x: number, + y: number, + width: number, + height: number, + fillStyle: string, + rotate: number = 0, + ): void => { + const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement; + const ctx = canvas.getContext("2d")!; + ctx.save(); + ctx.translate(x, y); + ctx.rotate((rotate * Math.PI) / 180); + ctx.fillStyle = fillStyle; + ctx.fillRect(0, 0, width, height); + ctx.restore(); + } + // ============================================================= // OSC Functions // ============================================================= diff --git a/src/DomElements.ts b/src/DomElements.ts index a7844e0..1a457e4 100644 --- a/src/DomElements.ts +++ b/src/DomElements.ts @@ -52,6 +52,7 @@ export const singleElements = { error_line: "error_line", hydra_canvas: "hydra-bg", feedback: "feedback", + drawings: "drawings", scope: "scope", }; diff --git a/src/Utils/Generic.ts b/src/Utils/Generic.ts index ee69e4c..0043e7d 100644 --- a/src/Utils/Generic.ts +++ b/src/Utils/Generic.ts @@ -69,6 +69,15 @@ export function arrayOfObjectsToObjectWithArrays>( ); } +export function maybeAtomic(value: T): T | T[] { + /* + * Returns first value of array if array of length 1, otherwise returns value + * @param {any} value - Value to check + * @returns {any} Value or array + */ + return Array.isArray(value) && value.length === 1 ? value[0] : value; +} + export function filterObject( obj: Record, filter: string[], diff --git a/src/WindowBehavior.ts b/src/WindowBehavior.ts index 89462f3..e42ef35 100644 --- a/src/WindowBehavior.ts +++ b/src/WindowBehavior.ts @@ -44,6 +44,9 @@ export const installWindowBehaviors = ( window.addEventListener("resize", () => handleResize(app.interface.feedback as HTMLCanvasElement), ); + window.addEventListener("resize", () => + handleResize(app.interface.drawings as HTMLCanvasElement), +); window.addEventListener("beforeunload", (event) => { event.preventDefault(); saveBeforeExit(app); diff --git a/src/classes/AbstractEvents.ts b/src/classes/AbstractEvents.ts index efbe666..4d34d44 100644 --- a/src/classes/AbstractEvents.ts +++ b/src/classes/AbstractEvents.ts @@ -463,6 +463,16 @@ export abstract class AudibleEvent extends AbstractEvent { return this; } + public draw = (lambda: Function) => { + lambda(this.values); + return this; + } + + public clear = () => { + this.app.api.clear(); + return this; + } + freq = (value: number | number[], ...kwargs: number[]): this => { /* * This function is used to set the frequency of the Event. diff --git a/src/classes/MidiEvent.ts b/src/classes/MidiEvent.ts index b59c8c2..ace416b 100644 --- a/src/classes/MidiEvent.ts +++ b/src/classes/MidiEvent.ts @@ -6,6 +6,7 @@ import { filterObject, arrayOfObjectsToObjectWithArrays, objectWithArraysToArrayOfObjects, + maybeAtomic, } from "../Utils/Generic"; export type MidiParams = { @@ -110,8 +111,8 @@ export class MidiEvent extends AudibleEvent { const newArrays = arrayOfObjectsToObjectWithArrays(events) as MidiParams; - this.values.note = newArrays.note; - if (newArrays.bend) this.values.bend = newArrays.bend; + this.values.note = maybeAtomic(newArrays.note); + if (newArrays.bend) this.values.bend = maybeAtomic(newArrays.bend); }; out = (): void => { diff --git a/src/classes/SoundEvent.ts b/src/classes/SoundEvent.ts index 664aea9..0b0cadd 100644 --- a/src/classes/SoundEvent.ts +++ b/src/classes/SoundEvent.ts @@ -5,6 +5,7 @@ import { filterObject, arrayOfObjectsToObjectWithArrays, objectWithArraysToArrayOfObjects, + maybeAtomic, } from "../Utils/Generic"; import { midiToFreq, resolvePitchClass } from "zifferjs"; @@ -414,11 +415,11 @@ export class SoundEvent extends AudibleEvent { const newArrays = arrayOfObjectsToObjectWithArrays(events) as SoundParams; - this.values.note = newArrays.note; - this.values.freq = newArrays.freq; - this.values.pitch = newArrays.pitch; - this.values.octave = newArrays.octave; - this.values.pitchOctave = newArrays.pitchOctave; + this.values.note = maybeAtomic(newArrays.note); + this.values.freq = maybeAtomic(newArrays.freq); + this.values.pitch = maybeAtomic(newArrays.pitch); + this.values.octave = maybeAtomic(newArrays.octave); + this.values.pitchOctave = maybeAtomic(newArrays.pitchOctave); }; diff --git a/src/documentation/patterns/generators.ts b/src/documentation/patterns/generators.ts new file mode 100644 index 0000000..c397f64 --- /dev/null +++ b/src/documentation/patterns/generators.ts @@ -0,0 +1,37 @@ +import { type Editor } from "../../main"; +import { makeExampleFactory } from "../../Documentation"; + +export const generators = (application: Editor): string => { + const makeExample = makeExampleFactory(application); + return ` +# Generator functions + + +${makeExample( + "More complex function generating chaotic frequencies", + ` + function* strange(x = 0.1, y = 0, z = 0, rho = 28, beta = 8 / 3, zeta = 10) { + while (true) { + const dx = 10 * (y - x); + const dy = x * (rho - z) - y; + const dz = x * y - beta * z; + + x += dx * 0.01; + y += dy * 0.01; + z += dz * 0.01; + + const value = 300 + 30 * (Math.sin(x) + Math.tan(y) + Math.cos(z)) + yield value; + } + } + + beat(0.25) :: sound("triangle") + .freq(cache("stranger",strange(3,5,2))) + .adsr(.15,.1,.1,.1) + .log("freq").out() + `, + true, +)}; + +`; +}; diff --git a/src/documentation/patterns/ziffers/ziffers_basics.ts b/src/documentation/patterns/ziffers/ziffers_basics.ts index ecb2d96..21bd6ab 100644 --- a/src/documentation/patterns/ziffers/ziffers_basics.ts +++ b/src/documentation/patterns/ziffers/ziffers_basics.ts @@ -190,7 +190,7 @@ ${makeExample( )} ${makeExample( - "Chord transposition with roman numerals", + "Chord inversions with roman numerals", ` z1('i i v%-4 v%-2 vi%-5 vi%-3 iv%-2 iv%-1') .sound('triangle').adsr(1/16, 1/5, 0.1, 0) @@ -201,7 +201,7 @@ ${makeExample( )} ${makeExample( - "Chord transposition with named chords", + "Chord inversion with named chords", ` z1('1/4 Cmin!3 Fmin!3 Fmin%-1 Fmin%-2 Fmin%-1') .sound("sine").bpf(500 + usine(1/4) * 2000) diff --git a/src/main.ts b/src/main.ts index acff630..ce043f7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -123,6 +123,7 @@ export class Editor { this.initializeButtonGroups(); this.setCanvas(this.interface.feedback as HTMLCanvasElement); this.setCanvas(this.interface.scope as HTMLCanvasElement); + this.setCanvas(this.interface.drawings as HTMLCanvasElement); try { this.loadHydraSynthAsync(); } catch (error) {