@audio-ui/utils 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+
2
+ $ tsup
3
+ CLI Building entry: {"index":"src/index.ts"}
4
+ CLI Using tsconfig: tsconfig.json
5
+ CLI tsup v8.5.1
6
+ CLI Using tsup config: /Users/lucienloua/Desktop/audio-ui/packages/utils/tsup.config.ts
7
+ CLI Target: es2022
8
+ CLI Cleaning output folder
9
+ ESM Build start
10
+ ESM dist/index.js 9.31 KB
11
+ ESM dist/index.js.map 20.40 KB
12
+ ESM ⚡️ Build success in 18ms
13
+ DTS Build start
14
+ DTS ⚡️ Build success in 1684ms
15
+ DTS dist/index.d.ts 5.34 KB
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @audio-ui/utils
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e649721: init
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # `@audio-ui/utils`
2
+
3
+ A collection of utility functions for Audio UI.
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@audio-ui/utils",
3
+ "version": "0.0.1",
4
+ "description": "A collection of utility functions for Audio UI.",
5
+ "license": "MIT",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/ouestlabs/audio-ui.git",
15
+ "directory": "packages/utils"
16
+ },
17
+ "homepage": "https://github.com/ouestlabs/audio-ui/tree/main/packages/utils",
18
+ "bugs": {
19
+ "url": "https://github.com/ouestlabs/audio-ui/issues"
20
+ },
21
+ "keywords": [
22
+ "audio-ui",
23
+ "utils"
24
+ ],
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ },
30
+ "./dom": {
31
+ "types": "./dist/dom/index.d.ts",
32
+ "import": "./dist/dom/index.js"
33
+ }
34
+ },
35
+ "scripts": {
36
+ "build:pkg": "tsup",
37
+ "dev": "tsup --watch"
38
+ },
39
+ "devDependencies": {
40
+ "@audio-ui/typescript": "workspace:*",
41
+ "@types/bun": "latest",
42
+ "tsup": "^8.5.0"
43
+ }
44
+ }
package/src/geom.ts ADDED
@@ -0,0 +1,335 @@
1
+ import { clamp } from "./math";
2
+ import { type int, Unhandled } from "./std";
3
+
4
+ export type Point = { x: number; y: number };
5
+ export type Circle = Point & { r: number };
6
+ export type Size = { width: number; height: number };
7
+ export type Rect = Point & Size;
8
+ export type Padding = [number, number, number, number];
9
+ export type Client = { clientX: number; clientY: number };
10
+
11
+ export enum Axis {
12
+ T = 0,
13
+ R = 1,
14
+ B = 2,
15
+ L = 3,
16
+ }
17
+
18
+ export enum Corner {
19
+ TL = 0,
20
+ TR = 1,
21
+ BR = 2,
22
+ BL = 3,
23
+ }
24
+
25
+ export namespace Geom {
26
+ export const outerTangentPoints = (a: Circle, b: Circle): [Point, Point] => {
27
+ const dx = b.x - a.x;
28
+ const dy = b.y - a.y;
29
+ const angle =
30
+ Math.atan2(dy, dx) +
31
+ Math.acos((a.r - b.r) / Math.sqrt(dx * dx + dy * dy));
32
+ const cs = Math.cos(angle);
33
+ const sn = Math.sin(angle);
34
+ return [
35
+ { x: a.x + a.r * cs, y: a.y + a.r * sn },
36
+ { x: b.x + b.r * cs, y: b.y + b.r * sn },
37
+ ];
38
+ };
39
+ }
40
+
41
+ export namespace Point {
42
+ export const zero = (): Point => ({ x: 0, y: 0 });
43
+ export const create = (x: number, y: number): Point => ({ x, y });
44
+ export const clone = (point: Point): Point => ({ ...point });
45
+ export const floor = (point: Point): Point => ({
46
+ x: Math.floor(point.x),
47
+ y: Math.floor(point.y),
48
+ });
49
+ export const length = (point: Point): number =>
50
+ Math.sqrt(point.x * point.x + point.y * point.y);
51
+ export const distance = (a: Point, b: Point): number =>
52
+ Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2);
53
+ export const add = (a: Point, b: Point): Point => ({
54
+ x: a.x + b.x,
55
+ y: a.y + b.y,
56
+ });
57
+ export const subtract = (a: Point, b: Point): Point => ({
58
+ x: a.x - b.x,
59
+ y: a.y - b.y,
60
+ });
61
+ export const scaleBy = (point: Point, scale: number): Point => ({
62
+ x: point.x * scale,
63
+ y: point.y * scale,
64
+ });
65
+ export const scaleTo = (point: Point, scale: number): Point => {
66
+ const multiplier = scale / length(point);
67
+ return { x: point.x * multiplier, y: point.y * multiplier };
68
+ };
69
+ export const fromClient = (object: {
70
+ clientX: number;
71
+ clientY: number;
72
+ }): Point => ({
73
+ x: object.clientX,
74
+ y: object.clientY,
75
+ });
76
+ }
77
+
78
+ export namespace Rect {
79
+ export const Empty: Readonly<Rect> = Object.freeze({
80
+ x: 0,
81
+ y: 0,
82
+ width: 0,
83
+ height: 0,
84
+ });
85
+
86
+ export const corners = (rectangle: Rect): Array<Point> => {
87
+ const x0 = rectangle.x;
88
+ const y0 = rectangle.y;
89
+ const x1 = x0 + rectangle.width;
90
+ const y1 = y0 + rectangle.height;
91
+ return [
92
+ { x: x0, y: y0 },
93
+ { x: x1, y: y0 },
94
+ { x: x1, y: y1 },
95
+ { x: x0, y: y1 },
96
+ ];
97
+ };
98
+
99
+ export const inflate = (rect: Rect, amount: number): Rect => ({
100
+ x: rect.x - amount,
101
+ y: rect.y - amount,
102
+ width: rect.width + amount * 2.0,
103
+ height: rect.height + amount * 2.0,
104
+ });
105
+
106
+ export const contains = (outer: Rect, inner: Rect): boolean => {
107
+ const topLeftInside = inner.x >= outer.x && inner.y >= outer.y;
108
+ const bottomRightInside =
109
+ inner.x + inner.width <= outer.x + outer.width &&
110
+ inner.y + inner.height <= outer.y + outer.height;
111
+ return topLeftInside && bottomRightInside;
112
+ };
113
+
114
+ export const isPointInside = (point: Point, rect: Rect): boolean =>
115
+ point.x >= rect.x &&
116
+ point.x <= rect.x + rect.width &&
117
+ point.y >= rect.y &&
118
+ point.y <= rect.y + rect.height;
119
+
120
+ export const intersect = (a: Rect, b: Rect): boolean => {
121
+ const xMin = Math.max(a.x, b.x);
122
+ const xMax = Math.min(a.x + a.width, b.x + b.width);
123
+ const yMax = Math.min(a.y + a.height, b.y + b.height);
124
+ const yMin = Math.max(a.y, b.y);
125
+ return xMax > xMin && yMax > yMin;
126
+ };
127
+
128
+ export const axis = (rectangle: Rect, axisValue: Axis): number => {
129
+ switch (axisValue) {
130
+ case Axis.T:
131
+ return rectangle.y;
132
+ case Axis.R:
133
+ return rectangle.x + rectangle.width;
134
+ case Axis.B:
135
+ return rectangle.y + rectangle.height;
136
+ case Axis.L:
137
+ return rectangle.x;
138
+ default:
139
+ return Unhandled(axisValue);
140
+ }
141
+ };
142
+
143
+ export const corner = (rectangle: Rect, cornerValue: Corner): Point => {
144
+ switch (cornerValue) {
145
+ case Corner.TL:
146
+ return { x: rectangle.x, y: rectangle.y };
147
+ case Corner.TR:
148
+ return { x: rectangle.x + rectangle.width, y: rectangle.y };
149
+ case Corner.BR:
150
+ return {
151
+ x: rectangle.x + rectangle.width,
152
+ y: rectangle.y + rectangle.height,
153
+ };
154
+ case Corner.BL:
155
+ return { x: rectangle.x, y: rectangle.y + rectangle.height };
156
+ default:
157
+ return Unhandled(cornerValue);
158
+ }
159
+ };
160
+
161
+ export const center = (rectangle: Rect): Point => ({
162
+ x: rectangle.x + rectangle.width * 0.5,
163
+ y: rectangle.y + rectangle.height * 0.5,
164
+ });
165
+
166
+ export const isEmpty = (rectangle: Rect): boolean =>
167
+ rectangle.width === 0 || rectangle.height === 0;
168
+
169
+ export const union = (a: Rect, b: Readonly<Rect>): void => {
170
+ if (Rect.isEmpty(a)) {
171
+ if (!Rect.isEmpty(b)) {
172
+ a.x = b.x;
173
+ a.y = b.y;
174
+ a.width = b.width;
175
+ a.height = b.height;
176
+ }
177
+ } else if (!Rect.isEmpty(b)) {
178
+ const bx = b.x;
179
+ const by = b.y;
180
+ const ux = Math.min(a.x, bx);
181
+ const uy = Math.min(a.y, by);
182
+ a.width = Math.max(a.x + a.width, bx + b.width) - ux;
183
+ a.height = Math.max(a.y + a.height, by + b.height) - uy;
184
+ a.x = ux;
185
+ a.y = uy;
186
+ }
187
+ };
188
+ }
189
+
190
+ export interface AABB {
191
+ xMin: number;
192
+ xMax: number;
193
+ yMin: number;
194
+ yMax: number;
195
+ }
196
+
197
+ export namespace AABB {
198
+ export const width = (aabb: AABB): number => aabb.xMax - aabb.xMin;
199
+ export const height = (aabb: AABB): number => aabb.yMax - aabb.yMin;
200
+
201
+ export const from = (aabb: AABB, that: AABB): void => {
202
+ aabb.xMin = that.xMin;
203
+ aabb.xMax = that.xMax;
204
+ aabb.yMin = that.yMin;
205
+ aabb.yMax = that.yMax;
206
+ };
207
+
208
+ export const extend = (aabb: AABB, offset: number): void => {
209
+ aabb.xMin -= offset;
210
+ aabb.yMin -= offset;
211
+ aabb.xMax += offset;
212
+ aabb.yMax += offset;
213
+ };
214
+
215
+ export const padding = (
216
+ aabb: AABB,
217
+ [top, right, bottom, left]: Readonly<Padding>
218
+ ): AABB => {
219
+ aabb.xMin += left;
220
+ aabb.yMin += top;
221
+ aabb.xMax -= right;
222
+ aabb.yMax -= bottom;
223
+ return aabb;
224
+ };
225
+
226
+ export const intersectPoint = (aabb: AABB, point: Point): boolean =>
227
+ aabb.xMin <= point.x &&
228
+ point.x < aabb.xMax &&
229
+ aabb.yMin <= point.y &&
230
+ point.y < aabb.yMax;
231
+
232
+ export const intersectThat = (aabb: AABB, that: AABB): boolean =>
233
+ that.xMin < aabb.xMax &&
234
+ that.xMax > aabb.xMin &&
235
+ that.yMin < aabb.yMax &&
236
+ that.yMax > aabb.yMin;
237
+
238
+ export const center = (aabb: AABB): Point => ({
239
+ x: (aabb.xMin + aabb.xMax) * 0.5,
240
+ y: (aabb.yMin + aabb.yMax) * 0.5,
241
+ });
242
+ }
243
+
244
+ export namespace Padding {
245
+ export const Identity: Readonly<Padding> = Object.freeze([
246
+ 0.0, 0.0, 0.0, 0.0,
247
+ ]);
248
+ }
249
+
250
+ export namespace CohenSutherland {
251
+ export const intersects = (
252
+ xMin: number,
253
+ xMax: number,
254
+ yMin: number,
255
+ yMax: number,
256
+ x0: number,
257
+ y0: number,
258
+ x1: number,
259
+ y1: number
260
+ ): boolean => {
261
+ const c0 = code(xMin, xMax, yMin, yMax, x0, y0);
262
+ const c1 = code(xMin, xMax, yMin, yMax, x1, y1);
263
+ if ((c0 | c1) === 0) {
264
+ return false;
265
+ }
266
+ if ((c0 & c1) !== 0) {
267
+ return false;
268
+ }
269
+ const s = sign(x0, y0, x1, y1, xMin, yMin);
270
+ return (
271
+ s !== sign(x0, y0, x1, y1, xMax, yMin) ||
272
+ s !== sign(x0, y0, x1, y1, xMax, yMax) ||
273
+ s !== sign(x0, y0, x1, y1, xMin, yMax)
274
+ );
275
+ };
276
+
277
+ const code = (
278
+ xMin: number,
279
+ xMax: number,
280
+ yMin: number,
281
+ yMax: number,
282
+ x: number,
283
+ y: number
284
+ ): int => {
285
+ let codeValue = 0;
286
+ if (x <= xMin) {
287
+ codeValue |= 1;
288
+ } else if (x >= xMax) {
289
+ codeValue |= 2;
290
+ }
291
+ if (y <= yMin) {
292
+ codeValue |= 8;
293
+ } else if (y >= yMax) {
294
+ codeValue |= 4;
295
+ }
296
+ return codeValue;
297
+ };
298
+
299
+ const sign = (
300
+ x0: number,
301
+ y0: number,
302
+ x1: number,
303
+ y1: number,
304
+ x2: number,
305
+ y2: number
306
+ ): boolean => (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0) >= 0;
307
+ }
308
+
309
+ export interface ValueAxis {
310
+ valueToAxis(value: number): number;
311
+ axisToValue(axis: number): number;
312
+ }
313
+
314
+ export namespace ValueAxis {
315
+ export const Identity: ValueAxis = {
316
+ valueToAxis: (value: number): number => value,
317
+ axisToValue: (axis: number): number => axis,
318
+ };
319
+
320
+ export const toClamped = (
321
+ valueAxis: ValueAxis,
322
+ min: number,
323
+ max: number
324
+ ): ValueAxis => ({
325
+ valueToAxis: (value: number): number =>
326
+ valueAxis.valueToAxis(clamp(value, min, max)),
327
+ axisToValue: (axis: number): number =>
328
+ clamp(valueAxis.axisToValue(axis), min, max),
329
+ });
330
+
331
+ export const createClamped = (min: number, max: number): ValueAxis => ({
332
+ valueToAxis: (value: number): number => clamp(value, min, max),
333
+ axisToValue: (axis: number): number => clamp(axis, min, max),
334
+ });
335
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./geom";
2
+ export * from "./math";
3
+ export * from "./std";
package/src/math.ts ADDED
@@ -0,0 +1,34 @@
1
+ import type { int, unitValue } from "./std";
2
+
3
+ export const TAU = Math.PI * 2.0;
4
+ export const PI_HALF = Math.PI / 2.0;
5
+ export const PI_QUART = Math.PI / 4.0;
6
+ export const INVERSE_SQRT_2 = 1.0 / Math.sqrt(2.0);
7
+
8
+ export const clamp = (value: number, min: number, max: number): number =>
9
+ Math.max(min, Math.min(value, max));
10
+ export const clampUnit = (value: number): unitValue =>
11
+ Math.max(0.0, Math.min(value, 1.0));
12
+ export const squashUnit = (value: unitValue, margin: unitValue): unitValue =>
13
+ margin + (1.0 - 2.0 * margin) * Math.max(0.0, Math.min(value, 1.0));
14
+ export const quantizeFloor = (value: number, interval: number): number =>
15
+ Math.floor(value / interval) * interval;
16
+ export const quantizeCeil = (value: number, interval: number): number =>
17
+ Math.ceil(value / interval) * interval;
18
+ export const quantizeRound = (value: number, interval: number): number =>
19
+ Math.round(value / interval) * interval;
20
+ export const linear = (y1: number, y2: number, mu: number): number =>
21
+ y1 + (y2 - y1) * mu;
22
+ export const exponential = (y1: number, y2: number, mu: number): number =>
23
+ y1 * (y2 / y1) ** mu;
24
+ export const cosine = (y1: number, y2: number, mu: number): number => {
25
+ const mu2 = (1.0 - Math.cos(mu * Math.PI)) * 0.5;
26
+ return y1 * (1.0 - mu2) + y2 * mu2;
27
+ };
28
+ export const mod = (value: number, range: number): number =>
29
+ fract(value / range) * range;
30
+ export const fract = (value: number): number => value - Math.floor(value);
31
+ export const nextPowOf2 = (n: int): int =>
32
+ 2 ** Math.ceil(Math.log(n) / Math.log(2));
33
+ export const radToDeg = (rad: number): number => (rad * 180.0) / Math.PI;
34
+ export const degToRad = (deg: number): number => (deg / 180.0) * Math.PI;
package/src/std.ts ADDED
@@ -0,0 +1,49 @@
1
+ export type int = number;
2
+ export type unitValue = number; // 0...1
3
+ export type Optional<T> = T | undefined;
4
+ export type Nullable<T> = T | null;
5
+ export type Maybe<T> = T | undefined | null;
6
+ export type ValueOrProvider<T> = T | (() => T);
7
+ export type Procedure<T> = (value: T) => void;
8
+ export type Func<U, T> = (value: U) => T;
9
+ export type AnyFunc = (...args: any[]) => any;
10
+ export type AssertType<T> = (value: unknown) => value is T;
11
+ export type Exec = () => void;
12
+
13
+ export const isDefined = <T>(value: Maybe<T>): value is T =>
14
+ value !== undefined && value !== null;
15
+
16
+ export const isUndefined = (value: unknown): value is undefined =>
17
+ value === undefined;
18
+
19
+ export const isNotUndefined = <T>(value: Optional<T>): value is T =>
20
+ value !== undefined;
21
+
22
+ export const asDefined = <T>(
23
+ value: Maybe<T>,
24
+ fail: ValueOrProvider<string> = "asDefined failed"
25
+ ): T =>
26
+ value === null || value === undefined ? panic(getOrProvide(fail)) : value;
27
+
28
+ export const Unhandled = <R>(empty: never): R => {
29
+ throw new Error(`Unhandled ${empty}`);
30
+ };
31
+
32
+ export const panic = (issue?: string | Error | unknown): never => {
33
+ throw typeof issue === "string" ? new Error(issue) : issue;
34
+ };
35
+
36
+ export const getOrProvide = <T>(value: ValueOrProvider<T>): T =>
37
+ value instanceof Function ? value() : value;
38
+
39
+ export const EmptyExec: Exec = (): void => {
40
+ // no-op
41
+ };
42
+
43
+ export const EmptyProcedure: Procedure<any> = (_: any): void => {
44
+ // no-op
45
+ };
46
+
47
+ export function assertType<T>(_value: unknown): asserts _value is T {
48
+ // no-op
49
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "@audio-ui/typescript/base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ESNext", "DOM", "DOM.Iterable"]
5
+ },
6
+ "exclude": ["dist", "node_modules"]
7
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ index: "src/index.ts",
6
+ },
7
+ format: ["esm"],
8
+ dts: true,
9
+ sourcemap: true,
10
+ clean: true,
11
+ });