added generative audio, tailwind, improvements, adjustments
This commit is contained in:
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nodejs 24.12.0
|
||||||
440
package-lock.json
generated
440
package-lock.json
generated
@@ -8,6 +8,8 @@
|
|||||||
"name": "hear-on-out",
|
"name": "hear-on-out",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"p5": "^2.1.2",
|
||||||
|
"p5-svelte": "^3.1.2",
|
||||||
"tonal": "^6.4.2",
|
"tonal": "^6.4.2",
|
||||||
"tone": "^15.1.22"
|
"tone": "^15.1.22"
|
||||||
},
|
},
|
||||||
@@ -20,19 +22,21 @@
|
|||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||||
"@vitest/browser": "^3.2.3",
|
"@vitest/browser": "^3.2.3",
|
||||||
|
"autoprefixer": "^10.4.23",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-svelte": "^3.0.0",
|
"eslint-plugin-svelte": "^3.0.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"openmeteo": "^1.2.0",
|
"openmeteo": "^1.2.0",
|
||||||
"playwright": "^1.53.0",
|
"playwright": "^1.53.0",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.0.0",
|
||||||
"svelte-geolocation": "^1.0.0",
|
"svelte-geolocation": "^1.0.0",
|
||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.1.18",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.20.0",
|
||||||
"vite": "^6.2.6",
|
"vite": "^6.2.6",
|
||||||
@@ -89,6 +93,12 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@davepagurek/bezier-path": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@davepagurek/bezier-path/-/bezier-path-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-4L9ddgzZc9DRGyl1RrS3z5nwnVJoyjsAelVG4X1jh4tVxryEHr4H9QavhxW/my6Rn3669Qz6mhv8gd5O/WeFTA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.5",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
||||||
@@ -778,6 +788,12 @@
|
|||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@japont/unicode-range": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@japont/unicode-range/-/unicode-range-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-BckHvA2XdjRBVAWe2uceNuRf78lBeI28kyWEbfr/Q2pE17POkwuZ6WWY/UMv8FL9iBxhW4xfDoNLM9UVZaTeUQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.8",
|
"version": "0.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||||
@@ -1195,6 +1211,7 @@
|
|||||||
"integrity": "sha512-DJm0UxVgzXq+1MUfiJK4Ridk7oIQsIets6JwHiEl97sI6nXScfXe+BeqNhzB7jQIVBb3BM51U4hNk8qQxRXBAA==",
|
"integrity": "sha512-DJm0UxVgzXq+1MUfiJK4Ridk7oIQsIets6JwHiEl97sI6nXScfXe+BeqNhzB7jQIVBb3BM51U4hNk8qQxRXBAA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/acorn-typescript": "^1.0.5",
|
"@sveltejs/acorn-typescript": "^1.0.5",
|
||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
@@ -1228,6 +1245,7 @@
|
|||||||
"integrity": "sha512-wojIS/7GYnJDYIg1higWj2ROA6sSRWvcR1PO/bqEyFr/5UZah26c8Cz4u0NaqjPeVltzsVpt2Tm8d2io0V+4Tw==",
|
"integrity": "sha512-wojIS/7GYnJDYIg1higWj2ROA6sSRWvcR1PO/bqEyFr/5UZah26c8Cz4u0NaqjPeVltzsVpt2Tm8d2io0V+4Tw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
|
"@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
|
||||||
"debug": "^4.4.1",
|
"debug": "^4.4.1",
|
||||||
@@ -1278,6 +1296,13 @@
|
|||||||
"tailwindcss": "4.1.10"
|
"tailwindcss": "4.1.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/node/node_modules/tailwindcss": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/oxide": {
|
"node_modules/@tailwindcss/oxide": {
|
||||||
"version": "4.1.10",
|
"version": "4.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz",
|
||||||
@@ -1539,12 +1564,20 @@
|
|||||||
"vite": "^5.2.0 || ^6"
|
"vite": "^5.2.0 || ^6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/vite/node_modules/tailwindcss": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@testing-library/dom": {
|
"node_modules/@testing-library/dom": {
|
||||||
"version": "10.4.0",
|
"version": "10.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
|
||||||
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
|
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.10.4",
|
"@babel/code-frame": "^7.10.4",
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
@@ -1907,6 +1940,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/p5": {
|
||||||
|
"version": "1.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/p5/-/p5-1.7.7.tgz",
|
||||||
|
"integrity": "sha512-WFuP7jqc5CkkMtCK/NphgvMnJz1Qi9CMuK7t6xLu/tuXkRdGQA4q4AD0dUYcChC0Oibe8PE8gbKSFPNF0BqVNw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.34.1",
|
"version": "8.34.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
||||||
@@ -1953,6 +1993,7 @@
|
|||||||
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.34.1",
|
"@typescript-eslint/scope-manager": "8.34.1",
|
||||||
"@typescript-eslint/types": "8.34.1",
|
"@typescript-eslint/types": "8.34.1",
|
||||||
@@ -2183,6 +2224,7 @@
|
|||||||
"integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==",
|
"integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
@@ -2332,8 +2374,8 @@
|
|||||||
"version": "8.15.0",
|
"version": "8.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2351,6 +2393,18 @@
|
|||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/acorn-walk": {
|
||||||
|
"version": "8.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||||
|
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": "^8.11.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
@@ -2434,6 +2488,43 @@
|
|||||||
"node": ">=18.2.0"
|
"node": ">=18.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/autoprefixer": {
|
||||||
|
"version": "10.4.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
|
||||||
|
"integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"browserslist": "^4.28.1",
|
||||||
|
"caniuse-lite": "^1.0.30001760",
|
||||||
|
"fraction.js": "^5.3.4",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"postcss-value-parser": "^4.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"autoprefixer": "bin/autoprefixer"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
@@ -2451,6 +2542,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/baseline-browser-mapping": {
|
||||||
|
"version": "2.9.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
|
||||||
|
"integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"baseline-browser-mapping": "dist/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
@@ -2475,6 +2576,41 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/browserslist": {
|
||||||
|
"version": "4.28.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
||||||
|
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
|
"caniuse-lite": "^1.0.30001759",
|
||||||
|
"electron-to-chromium": "^1.5.263",
|
||||||
|
"node-releases": "^2.0.27",
|
||||||
|
"update-browserslist-db": "^1.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"browserslist": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cac": {
|
"node_modules/cac": {
|
||||||
"version": "6.7.14",
|
"version": "6.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||||
@@ -2495,6 +2631,27 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/caniuse-lite": {
|
||||||
|
"version": "1.0.30001761",
|
||||||
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz",
|
||||||
|
"integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "CC-BY-4.0"
|
||||||
|
},
|
||||||
"node_modules/chai": {
|
"node_modules/chai": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
|
||||||
@@ -2595,6 +2752,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/colorjs.io": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -2719,6 +2882,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/electron-to-chromium": {
|
||||||
|
"version": "1.5.267",
|
||||||
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||||
|
"integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.18.1",
|
"version": "5.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||||
@@ -2781,6 +2951,16 @@
|
|||||||
"@esbuild/win32-x64": "0.25.5"
|
"@esbuild/win32-x64": "0.25.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escalade": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
@@ -2794,12 +2974,34 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escodegen": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"esprima": "^4.0.1",
|
||||||
|
"estraverse": "^5.2.0",
|
||||||
|
"esutils": "^2.0.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"escodegen": "bin/escodegen.js",
|
||||||
|
"esgenerate": "bin/esgenerate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"source-map": "~0.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.29.0",
|
"version": "9.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
|
||||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -2960,6 +3162,19 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/esprima": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"bin": {
|
||||||
|
"esparse": "bin/esparse.js",
|
||||||
|
"esvalidate": "bin/esvalidate.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esquery": {
|
"node_modules/esquery": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
||||||
@@ -3000,7 +3215,6 @@
|
|||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
@@ -3020,7 +3234,6 @@
|
|||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -3125,6 +3338,12 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-saver": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -3183,6 +3402,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/fraction.js": {
|
||||||
|
"version": "5.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
||||||
|
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/rawify"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
@@ -3198,6 +3431,12 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gifenc": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/gifenc/-/gifenc-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-xdr6AdrfGBcfzncONUOlXMBuc5wJDtOueE3c5rdG0oNgtINLD+f2iFZltrBRZYzACRbKr+mSVU/x98zv2u3jmw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@@ -3248,6 +3487,24 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "19.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.9.2.tgz",
|
||||||
|
"integrity": "sha512-0i6cuo6ER6usEOtKajUUDj92zlG+KArFia0857xxiEHAQcUwh/RtOQocui1LPJwunSYT574Pk64aNva1kwtxZg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-browser-languagedetector": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-KIToAzf8zwWvacgnRwJp63ase26o24AuNUlfNVJ5YZAFmdGhsJpmFClxXPuk9rv1FMI4lnc8zLSqgZPEZMrW4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.5.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -3427,6 +3684,12 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/libtess": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/libtess/-/libtess-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-Nps8HPeVVcsmJxUvFLKVJcCgcz+1ajPTXDVAVPs6+giOQP4AHV31uZFFkh+CKow/bkB7GbZWKmwmit7myaqDSw==",
|
||||||
|
"license": "SGI-B-2.0"
|
||||||
|
},
|
||||||
"node_modules/lightningcss": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.1",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||||
@@ -3875,6 +4138,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-releases": {
|
||||||
|
"version": "2.0.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||||
|
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/omggif": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/openmeteo": {
|
"node_modules/openmeteo": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/openmeteo/-/openmeteo-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/openmeteo/-/openmeteo-1.2.0.tgz",
|
||||||
@@ -3939,6 +4215,54 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p5": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/p5/-/p5-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-M96K3FSwd31Sawsl9TzJ8kBZFuUy06eqRHpWw0DIQAOmYOosVyPr7Eh5GR2LsScba0X2wSnfRBYMz64oVQVOdg==",
|
||||||
|
"license": "LGPL-2.1",
|
||||||
|
"dependencies": {
|
||||||
|
"@davepagurek/bezier-path": "^0.0.2",
|
||||||
|
"@japont/unicode-range": "^1.0.0",
|
||||||
|
"acorn": "^8.12.1",
|
||||||
|
"acorn-walk": "^8.3.4",
|
||||||
|
"colorjs.io": "^0.5.2",
|
||||||
|
"escodegen": "^2.1.0",
|
||||||
|
"file-saver": "^1.3.8",
|
||||||
|
"gifenc": "^1.0.3",
|
||||||
|
"i18next": "^19.0.2",
|
||||||
|
"i18next-browser-languagedetector": "^4.0.1",
|
||||||
|
"libtess": "^1.2.2",
|
||||||
|
"omggif": "^1.0.10",
|
||||||
|
"pako": "^2.1.0",
|
||||||
|
"pixelmatch": "^7.1.0",
|
||||||
|
"zod": "^3.25.51"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p5-svelte": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/p5-svelte/-/p5-svelte-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-lcfWh+cJ1/wRdIXHnjpYmDgj2h3TCy1QJVQnf/cBcFWS8CSkvyAN5F8u8H2U8qBUtZ4XaD3nd+1NoYUMHaMExQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p5": "^1.4.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/p5": "^1.4.2",
|
||||||
|
"p5": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p5-svelte/node_modules/p5": {
|
||||||
|
"version": "1.11.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/p5/-/p5-1.11.11.tgz",
|
||||||
|
"integrity": "sha512-k58mfexvavFb+KNRpi70PbkKE2gCNiWQkzS4kVOyC2F9SKGgYy1jSO+JXZ24ikXV9OvZIAxGusiSVWEijYrmNg==",
|
||||||
|
"license": "LGPL-2.1"
|
||||||
|
},
|
||||||
|
"node_modules/pako": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||||
|
"license": "(MIT AND Zlib)"
|
||||||
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@@ -4002,6 +4326,7 @@
|
|||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -4009,12 +4334,25 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pixelmatch": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"pngjs": "^7.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"pixelmatch": "bin/pixelmatch"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.53.1",
|
"version": "1.53.1",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz",
|
||||||
"integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==",
|
"integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.53.1"
|
"playwright-core": "1.53.1"
|
||||||
},
|
},
|
||||||
@@ -4041,6 +4379,15 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pngjs": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
@@ -4061,6 +4408,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -4178,6 +4526,13 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -4194,6 +4549,7 @@
|
|||||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -4210,6 +4566,7 @@
|
|||||||
"integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
|
"integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
|
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
|
||||||
@@ -4537,6 +4894,16 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -4624,6 +4991,7 @@
|
|||||||
"integrity": "sha512-5PEg+QQKce4t1qiOtVUhUS3AQRTtxJyGBTpxLcNWnr0Ve8q4r06bMo0Gv8uhtCPWlztZHoi3Ye7elLhu+PCTMg==",
|
"integrity": "sha512-5PEg+QQKce4t1qiOtVUhUS3AQRTtxJyGBTpxLcNWnr0Ve8q4r06bMo0Gv8uhtCPWlztZHoi3Ye7elLhu+PCTMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.3.0",
|
"@ampproject/remapping": "^2.3.0",
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@@ -4715,9 +5083,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.10",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||||
"integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==",
|
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -4912,6 +5280,7 @@
|
|||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -4943,6 +5312,37 @@
|
|||||||
"typescript": ">=4.8.4 <5.9.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/update-browserslist-db": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"escalade": "^3.2.0",
|
||||||
|
"picocolors": "^1.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"update-browserslist-db": "cli.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"browserslist": ">= 4.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uri-js": {
|
"node_modules/uri-js": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||||
@@ -4980,6 +5380,7 @@
|
|||||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
@@ -5125,6 +5526,7 @@
|
|||||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/chai": "^5.2.2",
|
"@types/chai": "^5.2.2",
|
||||||
"@vitest/expect": "3.2.4",
|
"@vitest/expect": "3.2.4",
|
||||||
@@ -5285,21 +5687,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
|
||||||
"version": "2.8.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
|
|
||||||
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"yaml": "bin.mjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
@@ -5319,6 +5706,15 @@
|
|||||||
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
|
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "3.25.76",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,19 +24,21 @@
|
|||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||||
"@vitest/browser": "^3.2.3",
|
"@vitest/browser": "^3.2.3",
|
||||||
|
"autoprefixer": "^10.4.23",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-svelte": "^3.0.0",
|
"eslint-plugin-svelte": "^3.0.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"openmeteo": "^1.2.0",
|
"openmeteo": "^1.2.0",
|
||||||
"playwright": "^1.53.0",
|
"playwright": "^1.53.0",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.0.0",
|
||||||
"svelte-geolocation": "^1.0.0",
|
"svelte-geolocation": "^1.0.0",
|
||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.1.18",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"typescript-eslint": "^8.20.0",
|
"typescript-eslint": "^8.20.0",
|
||||||
"vite": "^6.2.6",
|
"vite": "^6.2.6",
|
||||||
@@ -45,6 +47,8 @@
|
|||||||
"vitest-browser-svelte": "^0.1.0"
|
"vitest-browser-svelte": "^0.1.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"p5": "^2.1.2",
|
||||||
|
"p5-svelte": "^3.1.2",
|
||||||
"tonal": "^6.4.2",
|
"tonal": "^6.4.2",
|
||||||
"tone": "^15.1.22"
|
"tone": "^15.1.22"
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/app.css
14
src/app.css
@@ -1,7 +1,11 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
|
|
||||||
.syne-mono-regular {
|
@theme {
|
||||||
font-family: "Syne Mono", monospace;
|
--font-family-syne-mono: "Syne Mono", monospace;
|
||||||
font-weight: 400;
|
}
|
||||||
font-style: normal;
|
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
font-family: var(--font-family-syne-mono);
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body data-sveltekit-preload-data="hover" class="bg-black text-white syne-mono-regular">
|
<body data-sveltekit-preload-data="hover">
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
35
src/lib/audio/audio-effects.ts
Normal file
35
src/lib/audio/audio-effects.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import * as Tone from 'tone';
|
||||||
|
|
||||||
|
export function createReverb(wetValue: number): Tone.Reverb {
|
||||||
|
const reverb = new Tone.Reverb({
|
||||||
|
decay: 16,
|
||||||
|
preDelay: 0.5
|
||||||
|
});
|
||||||
|
reverb.wet.value = wetValue;
|
||||||
|
return reverb;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDelay(delayTime: string, feedback: number): Tone.FeedbackDelay {
|
||||||
|
const delay = new Tone.FeedbackDelay({
|
||||||
|
delayTime: delayTime,
|
||||||
|
feedback: feedback
|
||||||
|
});
|
||||||
|
delay.wet.value = 0.5;
|
||||||
|
return delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFilter(frequency: number, resonance: number): Tone.Filter {
|
||||||
|
return new Tone.Filter({
|
||||||
|
type: 'lowpass',
|
||||||
|
frequency: frequency,
|
||||||
|
Q: resonance
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createGain(volume: number): Tone.Gain {
|
||||||
|
return new Tone.Gain(Tone.dbToGain(volume));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createAnalyser(): Tone.Analyser {
|
||||||
|
return new Tone.Analyser('fft', 512);
|
||||||
|
}
|
||||||
61
src/lib/audio/chord-progressions.ts
Normal file
61
src/lib/audio/chord-progressions.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export type ChordProgression = Array<{
|
||||||
|
time: string;
|
||||||
|
notes: string[];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
// Bright, uplifting - for pleasant daytime conditions
|
||||||
|
export const brightProgression: ChordProgression = [
|
||||||
|
{ time: '0:0:0', notes: ['C4', 'E4', 'G4', 'B4'] }, // Cmaj7
|
||||||
|
{ time: '0:1:0', notes: ['F4', 'A4', 'C5', 'E5'] }, // Fmaj7
|
||||||
|
{ time: '0:2:0', notes: ['G4', 'B4', 'D5', 'F5'] }, // G7
|
||||||
|
{ time: '0:3:0', notes: ['A3', 'C4', 'E4', 'G4'] } // Am7
|
||||||
|
];
|
||||||
|
|
||||||
|
// Dreamy, calm - for pleasant nighttime conditions
|
||||||
|
export const dreamyProgression: ChordProgression = [
|
||||||
|
{ time: '0:0:0', notes: ['D4', 'F#4', 'A4', 'C5'] }, // Dmaj7
|
||||||
|
{ time: '0:1:0', notes: ['G3', 'B3', 'D4', 'F#4'] }, // Gmaj7
|
||||||
|
{ time: '0:2:0', notes: ['E4', 'G4', 'B4', 'D5'] }, // Em7
|
||||||
|
{ time: '0:3:0', notes: ['A3', 'C#4', 'E4', 'G4'] } // A7
|
||||||
|
];
|
||||||
|
|
||||||
|
// Melancholic, introspective - for cold/rainy conditions
|
||||||
|
export const melancholicProgression: ChordProgression = [
|
||||||
|
{ time: '0:0:0', notes: ['A3', 'C4', 'E4', 'G4'] }, // Am7
|
||||||
|
{ time: '0:1:0', notes: ['D3', 'F3', 'A3', 'C4'] }, // Dm7
|
||||||
|
{ time: '0:2:0', notes: ['G3', 'Bb3', 'D4', 'F4'] }, // Gm7
|
||||||
|
{ time: '0:3:0', notes: ['C3', 'E3', 'G3', 'Bb3'] } // C7
|
||||||
|
];
|
||||||
|
|
||||||
|
// Tense, atmospheric - for stormy/extreme conditions
|
||||||
|
export const tenseProgression: ChordProgression = [
|
||||||
|
{ time: '0:0:0', notes: ['E3', 'G3', 'Bb3', 'D4'] }, // Em7b5
|
||||||
|
{ time: '0:1:0', notes: ['F3', 'Ab3', 'C4', 'Eb4'] }, // Fm7
|
||||||
|
{ time: '0:2:0', notes: ['Bb3', 'Db4', 'F4', 'Ab4'] }, // Bbm7
|
||||||
|
{ time: '0:3:0', notes: ['Eb3', 'Gb3', 'Bb3', 'Db4'] } // Ebm7
|
||||||
|
];
|
||||||
|
|
||||||
|
// Warm, intense - for hot conditions
|
||||||
|
export const warmProgression: ChordProgression = [
|
||||||
|
{ time: '0:0:0', notes: ['E4', 'G#4', 'B4', 'D5'] }, // E7
|
||||||
|
{ time: '0:1:0', notes: ['A3', 'C#4', 'E4', 'G4'] }, // A7
|
||||||
|
{ time: '0:2:0', notes: ['D4', 'F#4', 'A4', 'C5'] }, // D7
|
||||||
|
{ time: '0:3:0', notes: ['G3', 'B3', 'D4', 'F4'] } // G7
|
||||||
|
];
|
||||||
|
|
||||||
|
// Ethereal, floating - for foggy/misty conditions
|
||||||
|
export const etherealProgression: ChordProgression = [
|
||||||
|
{ time: '0:0:0', notes: ['F4', 'A4', 'C5', 'E5'] }, // Fmaj7
|
||||||
|
{ time: '0:1:0', notes: ['C4', 'E4', 'G4', 'B4'] }, // Cmaj7
|
||||||
|
{ time: '0:2:0', notes: ['G4', 'B4', 'D5', 'F#5'] }, // Gmaj7
|
||||||
|
{ time: '0:3:0', notes: ['D4', 'F#4', 'A4', 'C#5'] } // Dmaj7
|
||||||
|
];
|
||||||
|
|
||||||
|
export const allProgressions = [
|
||||||
|
brightProgression,
|
||||||
|
dreamyProgression,
|
||||||
|
melancholicProgression,
|
||||||
|
tenseProgression,
|
||||||
|
warmProgression,
|
||||||
|
etherealProgression
|
||||||
|
];
|
||||||
16
src/lib/audio/instruments/arpSynth.ts
Normal file
16
src/lib/audio/instruments/arpSynth.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import * as Tone from 'tone';
|
||||||
|
|
||||||
|
export function createArpSynth(volume: number): Tone.Synth {
|
||||||
|
return new Tone.Synth({
|
||||||
|
oscillator: {
|
||||||
|
type: 'triangle'
|
||||||
|
},
|
||||||
|
envelope: {
|
||||||
|
attack: 0.005,
|
||||||
|
decay: 0.2,
|
||||||
|
sustain: 0,
|
||||||
|
release: 0.3
|
||||||
|
},
|
||||||
|
volume: volume
|
||||||
|
});
|
||||||
|
}
|
||||||
16
src/lib/audio/instruments/bassSynth.ts
Normal file
16
src/lib/audio/instruments/bassSynth.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import * as Tone from 'tone';
|
||||||
|
|
||||||
|
export function createBassSynth(): Tone.Synth {
|
||||||
|
return new Tone.Synth({
|
||||||
|
oscillator: {
|
||||||
|
type: 'sine'
|
||||||
|
},
|
||||||
|
envelope: {
|
||||||
|
attack: 0.1,
|
||||||
|
decay: 0.3,
|
||||||
|
sustain: 0.8,
|
||||||
|
release: 2.0
|
||||||
|
},
|
||||||
|
volume: -20
|
||||||
|
});
|
||||||
|
}
|
||||||
16
src/lib/audio/instruments/noiseSynth.ts
Normal file
16
src/lib/audio/instruments/noiseSynth.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import * as Tone from 'tone';
|
||||||
|
|
||||||
|
export function createNoiseSynth(volume: number): Tone.NoiseSynth {
|
||||||
|
return new Tone.NoiseSynth({
|
||||||
|
noise: {
|
||||||
|
type: 'pink'
|
||||||
|
},
|
||||||
|
envelope: {
|
||||||
|
attack: 0.005,
|
||||||
|
decay: 0.1,
|
||||||
|
sustain: 0,
|
||||||
|
release: 0.1
|
||||||
|
},
|
||||||
|
volume: volume
|
||||||
|
});
|
||||||
|
}
|
||||||
16
src/lib/audio/instruments/padSynth.ts
Normal file
16
src/lib/audio/instruments/padSynth.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import * as Tone from 'tone';
|
||||||
|
|
||||||
|
export function createPadSynth(isDay: boolean): Tone.PolySynth {
|
||||||
|
return new Tone.PolySynth(Tone.Synth, {
|
||||||
|
oscillator: {
|
||||||
|
type: isDay ? 'triangle' : 'sine'
|
||||||
|
},
|
||||||
|
envelope: {
|
||||||
|
attack: 1.5,
|
||||||
|
decay: 1,
|
||||||
|
sustain: 0.7,
|
||||||
|
release: 1.0
|
||||||
|
},
|
||||||
|
volume: -20
|
||||||
|
});
|
||||||
|
}
|
||||||
21
src/lib/audio/instruments/pingSynth.ts
Normal file
21
src/lib/audio/instruments/pingSynth.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as Tone from 'tone';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a sharp, high-pitched ping synth for extreme weather indicators
|
||||||
|
* @param volume - Volume in dB
|
||||||
|
* @returns A configured Synth instance
|
||||||
|
*/
|
||||||
|
export function createPingSynth(volume: number): Tone.Synth {
|
||||||
|
return new Tone.Synth({
|
||||||
|
oscillator: {
|
||||||
|
type: 'triangle'
|
||||||
|
},
|
||||||
|
envelope: {
|
||||||
|
attack: 0.001, // Very sharp attack
|
||||||
|
decay: 0.05,
|
||||||
|
sustain: 0,
|
||||||
|
release: 0.1
|
||||||
|
},
|
||||||
|
volume: volume
|
||||||
|
});
|
||||||
|
}
|
||||||
97
src/lib/audio/weather-mood.ts
Normal file
97
src/lib/audio/weather-mood.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import {
|
||||||
|
brightProgression,
|
||||||
|
dreamyProgression,
|
||||||
|
melancholicProgression,
|
||||||
|
tenseProgression,
|
||||||
|
warmProgression,
|
||||||
|
etherealProgression,
|
||||||
|
type ChordProgression
|
||||||
|
} from './chord-progressions';
|
||||||
|
|
||||||
|
interface WeatherConditions {
|
||||||
|
temperature2m: number;
|
||||||
|
relativeHumidity2m: number;
|
||||||
|
cloudCover: number;
|
||||||
|
windSpeed10m: number;
|
||||||
|
precipitation: number;
|
||||||
|
isDay: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate a weather mood score based on various conditions
|
||||||
|
* Returns values between 0 (harsh/extreme) and 1 (pleasant)
|
||||||
|
*/
|
||||||
|
export function calculateComfortScore(conditions: WeatherConditions): number {
|
||||||
|
const { temperature2m, relativeHumidity2m, cloudCover, windSpeed10m, precipitation } =
|
||||||
|
conditions;
|
||||||
|
|
||||||
|
// Temperature comfort: ideal 15-25°C, drops off outside this range
|
||||||
|
let tempScore = 1.0;
|
||||||
|
if (temperature2m < 15) {
|
||||||
|
tempScore = Math.max(0, 1 - Math.abs(15 - temperature2m) / 30);
|
||||||
|
} else if (temperature2m > 25) {
|
||||||
|
tempScore = Math.max(0, 1 - Math.abs(temperature2m - 25) / 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Humidity comfort: ideal 40-60%, drops off outside
|
||||||
|
let humidityScore = 1.0;
|
||||||
|
if (relativeHumidity2m < 40) {
|
||||||
|
humidityScore = Math.max(0, relativeHumidity2m / 40);
|
||||||
|
} else if (relativeHumidity2m > 60) {
|
||||||
|
humidityScore = Math.max(0, 1 - (relativeHumidity2m - 60) / 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cloud cover: some clouds (30-70%) is pleasant, extremes less so
|
||||||
|
const cloudScore = 1 - Math.abs(cloudCover - 50) / 50;
|
||||||
|
|
||||||
|
// Wind: light breeze (0-15 km/h) is nice, strong wind less so
|
||||||
|
const windScore = Math.max(0, 1 - windSpeed10m / 40);
|
||||||
|
|
||||||
|
// Precipitation: any is somewhat unpleasant
|
||||||
|
const precipScore = Math.max(0, 1 - precipitation / 10);
|
||||||
|
|
||||||
|
// Weighted average
|
||||||
|
return (
|
||||||
|
tempScore * 0.35 +
|
||||||
|
humidityScore * 0.2 +
|
||||||
|
cloudScore * 0.15 +
|
||||||
|
windScore * 0.15 +
|
||||||
|
precipScore * 0.15
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the appropriate chord progression based on weather conditions
|
||||||
|
*/
|
||||||
|
export function selectChordProgression(conditions: WeatherConditions): ChordProgression {
|
||||||
|
const comfortScore = calculateComfortScore(conditions);
|
||||||
|
const { temperature2m, precipitation, cloudCover, isDay } = conditions;
|
||||||
|
|
||||||
|
// Stormy/extreme conditions (heavy rain, very harsh)
|
||||||
|
if (precipitation > 5 || comfortScore < 0.2) {
|
||||||
|
return tenseProgression;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very hot conditions
|
||||||
|
if (temperature2m > 30) {
|
||||||
|
return warmProgression;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cold/rainy/gloomy conditions
|
||||||
|
if (temperature2m < 5 || (precipitation > 1 && cloudCover > 70)) {
|
||||||
|
return melancholicProgression;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foggy/misty conditions (high humidity + clouds, low wind)
|
||||||
|
if (conditions.relativeHumidity2m > 80 && cloudCover > 60 && temperature2m > 5) {
|
||||||
|
return etherealProgression;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pleasant conditions - choose based on day/night
|
||||||
|
if (comfortScore > 0.6) {
|
||||||
|
return isDay ? brightProgression : dreamyProgression;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: slightly unpleasant but not extreme
|
||||||
|
return isDay ? dreamyProgression : melancholicProgression;
|
||||||
|
}
|
||||||
196
src/lib/components/AudioVisualization.svelte
Normal file
196
src/lib/components/AudioVisualization.svelte
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import P5 from 'p5-svelte';
|
||||||
|
import type p5 from 'p5';
|
||||||
|
import type * as Tone from 'tone';
|
||||||
|
|
||||||
|
let { isPlaying = false, width = 400, height = 400, analyser = null } = $props<{
|
||||||
|
isPlaying: boolean;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
analyser: Tone.Analyser | null;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
let particles: Particle[] = [];
|
||||||
|
const numParticles = 60;
|
||||||
|
let audioData: Float32Array | null = null;
|
||||||
|
|
||||||
|
class Particle {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
baseX: number;
|
||||||
|
baseY: number;
|
||||||
|
vx: number;
|
||||||
|
vy: number;
|
||||||
|
size: number;
|
||||||
|
alpha: number;
|
||||||
|
index: number;
|
||||||
|
|
||||||
|
constructor(p: p5, index: number) {
|
||||||
|
this.index = index;
|
||||||
|
this.x = p.random(p.width);
|
||||||
|
this.y = p.random(p.height);
|
||||||
|
this.baseX = this.x;
|
||||||
|
this.baseY = this.y;
|
||||||
|
this.vx = p.random(-0.5, 0.5);
|
||||||
|
this.vy = p.random(-0.5, 0.5);
|
||||||
|
this.size = p.random(2, 6);
|
||||||
|
this.alpha = p.random(100, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(p: p5, audioLevel: number, bass: number, mid: number) {
|
||||||
|
// Base movement - gentle constant speed
|
||||||
|
this.x += this.vx;
|
||||||
|
this.y += this.vy;
|
||||||
|
|
||||||
|
// Subtle audio reactive displacement
|
||||||
|
const displacement = audioLevel * 20;
|
||||||
|
const angle = p.noise(this.x * 0.01, this.y * 0.01, p.frameCount * 0.01) * p.TWO_PI;
|
||||||
|
this.x += p.cos(angle) * displacement * 0.05;
|
||||||
|
this.y += p.sin(angle) * displacement * 0.05;
|
||||||
|
|
||||||
|
// Subtle audio reactive size
|
||||||
|
this.size = p.map(bass + mid, 0, 2, 3, 10);
|
||||||
|
|
||||||
|
// Wrap around edges
|
||||||
|
if (this.x < -50) this.x = p.width + 50;
|
||||||
|
if (this.x > p.width + 50) this.x = -50;
|
||||||
|
if (this.y < -50) this.y = p.height + 50;
|
||||||
|
if (this.y > p.height + 50) this.y = -50;
|
||||||
|
}
|
||||||
|
|
||||||
|
display(p: p5, audioLevel: number) {
|
||||||
|
p.noStroke();
|
||||||
|
const dynamicAlpha = p.map(audioLevel, 0, 1, 120, 220);
|
||||||
|
p.fill(255, dynamicAlpha);
|
||||||
|
p.ellipse(this.x, this.y, this.size, this.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(p: p5, other: Particle, maxDist: number) {
|
||||||
|
const d = p.dist(this.x, this.y, other.x, other.y);
|
||||||
|
if (d < maxDist) {
|
||||||
|
const alpha = p.map(d, 0, maxDist, 100, 0);
|
||||||
|
p.stroke(255, alpha);
|
||||||
|
p.strokeWeight(1);
|
||||||
|
p.line(this.x, this.y, other.x, other.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sketch = (p: p5) => {
|
||||||
|
p.setup = () => {
|
||||||
|
p.createCanvas(width, height);
|
||||||
|
p.background(0);
|
||||||
|
|
||||||
|
// Initialize particles
|
||||||
|
particles = [];
|
||||||
|
for (let i = 0; i < numParticles; i++) {
|
||||||
|
particles.push(new Particle(p, i));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
p.draw = () => {
|
||||||
|
p.background(0, 30); // Fade effect
|
||||||
|
|
||||||
|
if (isPlaying && analyser) {
|
||||||
|
// Get FFT data (frequency analysis)
|
||||||
|
audioData = analyser.getValue() as Float32Array;
|
||||||
|
|
||||||
|
// Calculate audio metrics from FFT data
|
||||||
|
// FFT values are in decibels (negative values, typically -100 to 0)
|
||||||
|
let sum = 0;
|
||||||
|
let bass = 0;
|
||||||
|
let mid = 0;
|
||||||
|
let treble = 0;
|
||||||
|
|
||||||
|
const bassRange = Math.floor(audioData.length * 0.15); // Low frequencies
|
||||||
|
const midRange = Math.floor(audioData.length * 0.4); // Mid frequencies
|
||||||
|
|
||||||
|
for (let i = 0; i < audioData.length; i++) {
|
||||||
|
// Convert from decibels to linear scale (0-1)
|
||||||
|
// FFT returns values from -100 to 0 dB
|
||||||
|
const normalized = p.map(audioData[i], -100, -30, 0, 1, true);
|
||||||
|
sum += normalized;
|
||||||
|
|
||||||
|
if (i < bassRange) {
|
||||||
|
bass += normalized;
|
||||||
|
} else if (i < midRange) {
|
||||||
|
mid += normalized;
|
||||||
|
} else {
|
||||||
|
treble += normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Average and amplify moderately
|
||||||
|
let audioLevel = (sum / audioData.length) * 2;
|
||||||
|
bass = (bass / bassRange) * 2.5;
|
||||||
|
mid = (mid / (midRange - bassRange)) * 2;
|
||||||
|
treble = (treble / (audioData.length - midRange)) * 1.5;
|
||||||
|
|
||||||
|
// Clamp values
|
||||||
|
audioLevel = p.constrain(audioLevel, 0, 1);
|
||||||
|
bass = p.constrain(bass, 0, 1);
|
||||||
|
mid = p.constrain(mid, 0, 1);
|
||||||
|
treble = p.constrain(treble, 0, 1);
|
||||||
|
|
||||||
|
// Fixed connection distance for consistency
|
||||||
|
const connectionDist = 100;
|
||||||
|
|
||||||
|
// Update and display particles
|
||||||
|
for (let i = 0; i < particles.length; i++) {
|
||||||
|
particles[i].update(p, audioLevel, bass, mid);
|
||||||
|
particles[i].display(p, audioLevel);
|
||||||
|
|
||||||
|
// Connect nearby particles (non-reactive distance)
|
||||||
|
for (let j = i + 1; j < particles.length; j++) {
|
||||||
|
particles[i].connect(p, particles[j], connectionDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!isPlaying) {
|
||||||
|
// Static state when not playing
|
||||||
|
p.fill(255, 50);
|
||||||
|
p.noStroke();
|
||||||
|
p.textAlign(p.CENTER, p.CENTER);
|
||||||
|
p.textSize(16);
|
||||||
|
p.text('Press Play', p.width / 2, p.height / 2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
p.windowResized = () => {
|
||||||
|
p.resizeCanvas(width, height);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="visualization-container">
|
||||||
|
{#if isPlaying}
|
||||||
|
<P5 {sketch} />
|
||||||
|
{:else}
|
||||||
|
<div class="placeholder" style="width: {width}px; height: {height}px;">
|
||||||
|
<p>Press Play</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.visualization-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #000;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #000;
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,3 +1,238 @@
|
|||||||
<!-- TODO: ADD TONEJS GENERATOR -->
|
<script lang="ts">
|
||||||
<!-- https://www.npmjs.com/package/tonal -->
|
import * as Tone from 'tone';
|
||||||
<!-- https://www.npmjs.com/package/tone -->
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
import AudioVisualization from '$lib/components/AudioVisualization.svelte';
|
||||||
|
import { createNoiseSynth } from '$lib/audio/instruments/noiseSynth';
|
||||||
|
import { createReverb, createDelay, createGain, createAnalyser } from '$lib/audio/audio-effects';
|
||||||
|
|
||||||
|
// Component props - air quality values and shared weather params for effects
|
||||||
|
let {
|
||||||
|
dust = 0,
|
||||||
|
pm10 = 0,
|
||||||
|
pm25 = 0,
|
||||||
|
relativeHumidity2m = 50,
|
||||||
|
windSpeed10m = 0,
|
||||||
|
volume = -15
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
// Component state
|
||||||
|
let isPlaying = $state(false);
|
||||||
|
let isInitialized = $state(false);
|
||||||
|
|
||||||
|
// Audio components
|
||||||
|
let noiseSynth: Tone.NoiseSynth | null = null;
|
||||||
|
let reverb: Tone.Reverb | null = null;
|
||||||
|
let delay: Tone.FeedbackDelay | null = null;
|
||||||
|
let gain: Tone.Gain | null = null;
|
||||||
|
let loop: Tone.Loop | null = null;
|
||||||
|
let analyser: Tone.Analyser | null = null;
|
||||||
|
|
||||||
|
// Derive air quality index from pollution values
|
||||||
|
// Higher values = dirtier air = more frequent bursts
|
||||||
|
const airQualityIndex = $derived.by(() => {
|
||||||
|
// Combine all pollution metrics (weighted average)
|
||||||
|
// PM2.5 is most harmful, so weight it higher
|
||||||
|
const pollutionScore = (pm25 * 2 + pm10 + dust) / 4;
|
||||||
|
return Math.max(0, pollutionScore);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Burst interval: cleaner air = slower, dirtier air = faster (like geiger counter)
|
||||||
|
const burstInterval = $derived.by(() => {
|
||||||
|
// Map pollution: low pollution = 3-6s, high pollution = 0.3-1s
|
||||||
|
// Less intense than before
|
||||||
|
const minInterval = 0.3; // 300ms for very polluted
|
||||||
|
const maxInterval = 6.0; // 6s for clean air
|
||||||
|
const normalizedPollution = Math.min(airQualityIndex / 100, 1); // Normalize to 0-1
|
||||||
|
return maxInterval - normalizedPollution * (maxInterval - minInterval);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shared delay/reverb parameters (matching WeatherGen)
|
||||||
|
const reverbWet = $derived.by(() => {
|
||||||
|
const humidity = relativeHumidity2m ?? 50;
|
||||||
|
return Math.max(0.3, Math.min(1, humidity / 100));
|
||||||
|
});
|
||||||
|
|
||||||
|
const delayTime = $derived.by(() => {
|
||||||
|
const speed = windSpeed10m ?? 0;
|
||||||
|
return speed > 5 ? '4n' : '8n';
|
||||||
|
});
|
||||||
|
|
||||||
|
const delayFeedback = $derived.by(() => {
|
||||||
|
const speed = windSpeed10m ?? 0;
|
||||||
|
return Math.max(0.2, Math.min(0.7, (speed / 20) * 0.5 + 0.2));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize audio components
|
||||||
|
const initializeAudio = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
// Create instruments
|
||||||
|
noiseSynth = createNoiseSynth(volume);
|
||||||
|
|
||||||
|
// Create effects with much more spacious reverb for air quality
|
||||||
|
reverb = new Tone.Reverb({
|
||||||
|
decay: 30, // Very long decay for spacious sound
|
||||||
|
preDelay: 0.1
|
||||||
|
});
|
||||||
|
reverb.wet.value = 0.8; // Higher wet signal for more reverb
|
||||||
|
|
||||||
|
delay = createDelay(delayTime, delayFeedback);
|
||||||
|
gain = createGain(volume);
|
||||||
|
analyser = createAnalyser();
|
||||||
|
|
||||||
|
// Connect audio chain
|
||||||
|
noiseSynth.chain(delay, reverb, gain, analyser, Tone.Destination);
|
||||||
|
|
||||||
|
// Generate reverb impulse
|
||||||
|
await reverb.generate();
|
||||||
|
|
||||||
|
isInitialized = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize air quality generator:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the geiger-counter-like loop
|
||||||
|
const startLoop = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
if (!isInitialized) {
|
||||||
|
await initializeAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Tone.start();
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
loop.dispose();
|
||||||
|
loop = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a loop that triggers at random intervals
|
||||||
|
loop = new Tone.Loop((time) => {
|
||||||
|
if (noiseSynth) {
|
||||||
|
// Trigger noise burst
|
||||||
|
noiseSynth.triggerAttackRelease('16n', time);
|
||||||
|
|
||||||
|
// Schedule next burst with randomization
|
||||||
|
const baseInterval = burstInterval;
|
||||||
|
const randomFactor = 0.5 + Math.random(); // 0.5x to 1.5x variation
|
||||||
|
const nextBurstTime = baseInterval * randomFactor;
|
||||||
|
|
||||||
|
if (loop) {
|
||||||
|
loop.interval = nextBurstTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, burstInterval);
|
||||||
|
|
||||||
|
loop.start(0);
|
||||||
|
|
||||||
|
// Start transport if not already running
|
||||||
|
if (Tone.getTransport().state !== 'started') {
|
||||||
|
Tone.getTransport().start();
|
||||||
|
}
|
||||||
|
|
||||||
|
isPlaying = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error starting air quality loop:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stop the loop
|
||||||
|
const stopLoop = (): void => {
|
||||||
|
if (loop) {
|
||||||
|
loop.stop();
|
||||||
|
loop.dispose();
|
||||||
|
loop = null;
|
||||||
|
}
|
||||||
|
// Don't stop transport - let WeatherGen control it
|
||||||
|
isPlaying = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle playback
|
||||||
|
const togglePlayback = async (): Promise<void> => {
|
||||||
|
if (isPlaying) {
|
||||||
|
stopLoop();
|
||||||
|
} else {
|
||||||
|
await startLoop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reactive updates for environmental parameters
|
||||||
|
// Note: Reverb wet is fixed at 0.8 for spacious sound, not reactive to humidity
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (delay && isInitialized) {
|
||||||
|
delay.delayTime.value = delayTime;
|
||||||
|
delay.feedback.rampTo(delayFeedback, 0.5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (gain && isInitialized) {
|
||||||
|
gain.gain.rampTo(Tone.dbToGain(volume), 0.1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (loop && isPlaying) {
|
||||||
|
// Update loop interval dynamically
|
||||||
|
const baseInterval = burstInterval;
|
||||||
|
loop.interval = baseInterval;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
onMount(() => {
|
||||||
|
initializeAudio();
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (loop) {
|
||||||
|
loop.dispose();
|
||||||
|
}
|
||||||
|
if (noiseSynth) {
|
||||||
|
noiseSynth.dispose();
|
||||||
|
}
|
||||||
|
if (reverb) {
|
||||||
|
reverb.dispose();
|
||||||
|
}
|
||||||
|
if (delay) {
|
||||||
|
delay.dispose();
|
||||||
|
}
|
||||||
|
if (gain) {
|
||||||
|
gain.dispose();
|
||||||
|
}
|
||||||
|
if (analyser) {
|
||||||
|
analyser.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="mx-auto mt-8 grid max-w-6xl grid-cols-1 gap-8 border-t border-white/20 p-4 pt-8 md:grid-cols-[300px_1fr]"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<button
|
||||||
|
class="cursor-crosshair rounded-md border border-white/20 px-6 py-3 text-base transition-all duration-300 hover:border-white/40 disabled:cursor-not-allowed disabled:opacity-50 {isPlaying
|
||||||
|
? 'bg-white text-black'
|
||||||
|
: 'bg-transparent text-white'}"
|
||||||
|
onclick={togglePlayback}
|
||||||
|
disabled={!isInitialized}
|
||||||
|
>
|
||||||
|
{isPlaying ? 'Stop' : 'Start'} Air Quality Monitor
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if isPlaying}
|
||||||
|
<div class="flex flex-col gap-2 text-sm opacity-80">
|
||||||
|
<p class="m-0">PM2.5: {pm25.toFixed(1)} µg/m³</p>
|
||||||
|
<p class="m-0">PM10: {pm10.toFixed(1)} µg/m³</p>
|
||||||
|
<p class="m-0">Dust: {dust.toFixed(1)} µg/m³</p>
|
||||||
|
<p class="my-1 opacity-40">---</p>
|
||||||
|
<p class="m-0">Pollution Index: {airQualityIndex.toFixed(1)}</p>
|
||||||
|
<p class="m-0">Burst Interval: {burstInterval.toFixed(2)}s</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<AudioVisualization {isPlaying} {analyser} width={400} height={400} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,108 +1,181 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Tone from 'tone';
|
import * as Tone from 'tone';
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
import AudioVisualization from '$lib/components/AudioVisualization.svelte';
|
||||||
|
import { createPadSynth } from '$lib/audio/instruments/padSynth';
|
||||||
|
import { createArpSynth } from '$lib/audio/instruments/arpSynth';
|
||||||
|
import { createBassSynth } from '$lib/audio/instruments/bassSynth';
|
||||||
|
import { createPingSynth } from '$lib/audio/instruments/pingSynth';
|
||||||
|
import {
|
||||||
|
createReverb,
|
||||||
|
createDelay,
|
||||||
|
createFilter,
|
||||||
|
createGain,
|
||||||
|
createAnalyser
|
||||||
|
} from '$lib/audio/audio-effects';
|
||||||
|
import { selectChordProgression, calculateComfortScore } from '$lib/audio/weather-mood';
|
||||||
|
import type { ChordProgression } from '$lib/audio/chord-progressions';
|
||||||
|
|
||||||
// Component props with default values
|
// Component props with default values
|
||||||
let {temperature2m = 20, relativeHumidity2m = 50, cloudCover = 30, volume = -10, windSpeed10m = 0, isDay} = $props();
|
let {
|
||||||
|
temperature2m = 20,
|
||||||
|
relativeHumidity2m = 50,
|
||||||
|
cloudCover = 30,
|
||||||
|
precipitation = 0,
|
||||||
|
volume = -10,
|
||||||
|
windSpeed10m = 0,
|
||||||
|
isDay
|
||||||
|
} = $props();
|
||||||
|
|
||||||
// Component state using runes
|
// Component state using runes
|
||||||
let isPlaying = $state(false);
|
let isPlaying = $state(false);
|
||||||
let currentChordIndex = $state(0);
|
|
||||||
let isInitialized = $state(false);
|
let isInitialized = $state(false);
|
||||||
|
|
||||||
// Audio components
|
// Audio components
|
||||||
let synth: Tone.PolySynth | null = null;
|
let synth: Tone.PolySynth | null = null;
|
||||||
|
let arpSynth: Tone.Synth | null = null;
|
||||||
|
let pingSynth: Tone.Synth | null = null;
|
||||||
|
let bassSynth: Tone.Synth | null = null;
|
||||||
|
let arpSequence: Tone.Sequence | null = null;
|
||||||
|
let pingSequence: Tone.Sequence | null = null;
|
||||||
|
let bassSequence: Tone.Sequence | null = null;
|
||||||
let reverb: Tone.Reverb | null = null;
|
let reverb: Tone.Reverb | null = null;
|
||||||
let delay: Tone.FeedbackDelay | null = null;
|
let delay: Tone.FeedbackDelay | null = null;
|
||||||
|
let filter: Tone.Filter | null = null;
|
||||||
let phaser: Tone.Phaser | null = null;
|
let phaser: Tone.Phaser | null = null;
|
||||||
let sequence: Tone.Sequence | null = null;
|
let sequence: Tone.Sequence | null = null;
|
||||||
let gain: Tone.Gain | null = null;
|
let gain: Tone.Gain | null = null;
|
||||||
|
let analyser: Tone.Analyser | null = null;
|
||||||
|
|
||||||
|
// Select chord progression based on weather mood
|
||||||
|
const currentProgression: ChordProgression = $derived.by(() => {
|
||||||
|
return selectChordProgression({
|
||||||
|
temperature2m,
|
||||||
|
relativeHumidity2m,
|
||||||
|
cloudCover,
|
||||||
|
windSpeed10m,
|
||||||
|
precipitation,
|
||||||
|
isDay
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
//TODO - ADD DIFFERENT PROGRESSIONS
|
// Derived reactive values using runes with safe fallbacks
|
||||||
const chordProgressions = [
|
const bpm = $derived.by(() => {
|
||||||
[
|
const temp = temperature2m ?? 20;
|
||||||
{ time: "0:0:0", notes: ['C4', 'E4', 'G4', 'B4'] },
|
// BPM starts at 10 for 0°C and increases with temperature
|
||||||
{ time: "0:1:0", notes: ['A3', 'C4', 'E4', 'G4'] },
|
// Day: more energetic (2x scaling), Night: calmer (1x scaling)
|
||||||
{ time: "0:2:0", notes: ['F3', 'A3', 'C4', 'E4'] },
|
const tempAboveZero = Math.max(0, temp);
|
||||||
{ time: "0:3:0", notes: ['G3', 'B3', 'D4', 'F4'] }
|
const scaledBpm = isDay ? 10 + tempAboveZero * 2 : 10 + tempAboveZero;
|
||||||
],
|
return Math.max(10, Math.min(200, scaledBpm));
|
||||||
[
|
});
|
||||||
{ time: "0:0:0", notes: ['D4', 'F4', 'A4', 'C5'] },
|
|
||||||
{ time: "0:1:0", notes: ['G3', 'B3', 'D4', 'F4'] },
|
|
||||||
{ time: "0:2:0", notes: ['C4', 'E4', 'G4', 'B4'] },
|
|
||||||
{ time: "0:3:0", notes: ['A3', 'C4', 'E4', 'G4'] }
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{ time: "0:0:0", notes: ['E4', 'G4', 'B4', 'D5'] },
|
|
||||||
{ time: "0:1:0", notes: ['A3', 'C4', 'E4', 'G4'] },
|
|
||||||
{ time: "0:2:0", notes: ['D4', 'F4', 'A4', 'C5'] },
|
|
||||||
{ time: "0:3:0", notes: ['G3', 'B3', 'D4', 'F4'] }
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{ time: "0:0:0", notes: ['F3', 'A3', 'C4', 'E4'] },
|
|
||||||
{ time: "0:1:0", notes: ['E4', 'G4', 'B4', 'D5'] },
|
|
||||||
{ time: "0:2:0", notes: ['D4', 'F4', 'A4', 'C5'] },
|
|
||||||
{ time: "0:3:0", notes: ['C4', 'E4', 'G4', 'B4'] }
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
let currentProgression = $state(chordProgressions[0]);
|
const reverbWet = $derived.by(() => {
|
||||||
|
const humidity = relativeHumidity2m ?? 50;
|
||||||
|
// Ensure minimum 0.3 wet signal so reverb is always audible
|
||||||
|
return Math.max(0.3, Math.min(1, humidity / 100));
|
||||||
|
});
|
||||||
|
|
||||||
// Derived reactive values using runes
|
// Delay time: 8th note for calm, quarter note for windy
|
||||||
const bpm = $derived((isDay? temperature2m * 2 : temperature2m));
|
const delayTime = $derived.by(() => {
|
||||||
const reverbWet = $derived(relativeHumidity2m/100);
|
const speed = windSpeed10m ?? 0;
|
||||||
const delayWet = $derived(Math.round(windSpeed10m)/10);
|
return speed > 5 ? '4n' : '8n';
|
||||||
const phaserBase = $derived((1 / cloudCover) * 100);
|
});
|
||||||
|
|
||||||
|
// Delay feedback: stronger with more wind
|
||||||
|
const delayFeedback = $derived.by(() => {
|
||||||
|
const speed = windSpeed10m ?? 0;
|
||||||
|
// Map 0-20 m/s wind to 0.2-0.7 feedback range
|
||||||
|
return Math.max(0.2, Math.min(0.7, (speed / 20) * 0.5 + 0.2));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter cutoff: more clouds = darker/lower frequency
|
||||||
|
const filterCutoff = $derived.by(() => {
|
||||||
|
const cover = cloudCover ?? 30;
|
||||||
|
// Map cloud cover: 0% clouds = 8000Hz (bright), 100% clouds = 400Hz (dark)
|
||||||
|
return Math.max(400, Math.min(8000, 8000 - (cover / 100) * 7600));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter resonance: more wind = more resonant
|
||||||
|
const filterResonance = $derived.by(() => {
|
||||||
|
const speed = windSpeed10m ?? 0;
|
||||||
|
// Map 0-30 m/s wind to 1-18 resonance (Q factor)
|
||||||
|
return Math.max(1, Math.min(18, 1 + (speed / 30) * 17));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Arpeggio interval: slower in cold, faster in heat
|
||||||
|
const arpInterval = $derived.by(() => {
|
||||||
|
const temp = temperature2m ?? 20;
|
||||||
|
// Map temperature: <0°C = 1n (whole note), 30°C+ = 8n (eighth note)
|
||||||
|
if (temp < 0) return '1n';
|
||||||
|
if (temp < 10) return '2n';
|
||||||
|
if (temp < 20) return '4n';
|
||||||
|
return '8n';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Arpeggio volume: quieter in cold, louder in heat
|
||||||
|
const arpVolume = $derived.by(() => {
|
||||||
|
const temp = temperature2m ?? 20;
|
||||||
|
// Map temperature: <0°C = -22dB, 30°C+ = -10dB
|
||||||
|
const normalizedTemp = Math.max(0, Math.min(30, temp));
|
||||||
|
return -22 + (normalizedTemp / 30) * 12;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Weather extremity: 0 = pleasant, 1 = extreme conditions
|
||||||
|
const weatherExtremity = $derived.by(() => {
|
||||||
|
const comfortScore = calculateComfortScore({
|
||||||
|
temperature2m,
|
||||||
|
relativeHumidity2m,
|
||||||
|
cloudCover,
|
||||||
|
windSpeed10m,
|
||||||
|
precipitation,
|
||||||
|
isDay
|
||||||
|
});
|
||||||
|
return 1 - comfortScore; // Invert: higher = more extreme
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ping volume: quieter in pleasant weather, louder in extreme weather
|
||||||
|
const pingVolume = $derived.by(() => {
|
||||||
|
// Map extremity: 0 (pleasant) = -22dB (present), 1 (extreme) = -8dB (prominent)
|
||||||
|
return -22 + weatherExtremity * 14;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ping interval: slower in cold, faster in heat
|
||||||
|
const pingInterval = $derived.by(() => {
|
||||||
|
const temp = temperature2m ?? 20;
|
||||||
|
// Map temperature: 0°C = 1n (whole note, slow), 30°C = 16n (16th note, fast)
|
||||||
|
if (temp <= 0) return '1n';
|
||||||
|
if (temp <= 10) return '2n';
|
||||||
|
if (temp <= 20) return '4n';
|
||||||
|
if (temp <= 25) return '8n';
|
||||||
|
return '16n';
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize audio components
|
// Initialize audio components
|
||||||
const initializeAudio = async (): Promise<void> => {
|
const initializeAudio = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
// Create dreamy synth
|
// Create instruments
|
||||||
synth = new Tone.PolySynth(Tone.Synth, {
|
synth = createPadSynth(isDay);
|
||||||
oscillator: {
|
arpSynth = createArpSynth(arpVolume);
|
||||||
type: isDay? 'triangle' : 'sine',
|
pingSynth = createPingSynth(pingVolume);
|
||||||
},
|
bassSynth = createBassSynth();
|
||||||
envelope: {
|
|
||||||
attack: 1.5,
|
|
||||||
decay: 1,
|
|
||||||
sustain: 0.7,
|
|
||||||
release: 1.0,
|
|
||||||
},
|
|
||||||
volume: -20,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create reverb with long, dreamy tail
|
// Create effects
|
||||||
reverb = new Tone.Reverb({
|
reverb = createReverb(reverbWet);
|
||||||
decay: 16,
|
delay = createDelay(delayTime, delayFeedback);
|
||||||
wet: reverbWet,
|
filter = createFilter(filterCutoff, filterResonance);
|
||||||
preDelay: 0.5,
|
gain = createGain(volume);
|
||||||
});
|
analyser = createAnalyser();
|
||||||
|
|
||||||
|
// Connect audio chain using .chain() for clarity
|
||||||
delay = new Tone.FeedbackDelay({
|
synth.chain(filter, delay, reverb, gain, analyser, Tone.Destination);
|
||||||
delayTime: '0.5',
|
arpSynth.chain(filter, delay, reverb, gain);
|
||||||
feedback: delayWet
|
pingSynth.chain(filter, delay, reverb, gain);
|
||||||
})
|
bassSynth.chain(delay, reverb, gain);
|
||||||
|
|
||||||
// Create a phaser
|
|
||||||
phaser = new Tone.Phaser({
|
|
||||||
frequency : phaserBase,
|
|
||||||
octaves : 5,
|
|
||||||
baseFrequency : 350
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create gain node
|
|
||||||
gain = new Tone.Gain(Tone.dbToGain(volume));
|
|
||||||
|
|
||||||
// Connect audio chain
|
|
||||||
synth.connect(phaser).connect(delay).connect(reverb).connect(gain).toDestination();
|
|
||||||
|
|
||||||
// Generate reverb impulse
|
// Generate reverb impulse
|
||||||
await reverb.generate();
|
await reverb.generate();
|
||||||
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
console.log('Audio initialized successfully');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize audio:', error);
|
console.error('Failed to initialize audio:', error);
|
||||||
}
|
}
|
||||||
@@ -125,25 +198,104 @@
|
|||||||
// Set transport BPM
|
// Set transport BPM
|
||||||
Tone.getTransport().bpm.value = bpm;
|
Tone.getTransport().bpm.value = bpm;
|
||||||
|
|
||||||
let progressionChangeCounter = 0;
|
sequence = new Tone.Sequence(
|
||||||
|
(time: number, chord) => {
|
||||||
sequence = new Tone.Sequence((time: number, chord) => {
|
|
||||||
if (synth && chord) {
|
if (synth && chord) {
|
||||||
synth!.triggerAttackRelease(chord.notes, '4n', time);
|
synth!.triggerAttackRelease(chord.notes, '4n', time);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
progressionChangeCounter++;
|
currentProgression,
|
||||||
|
'4n'
|
||||||
//Change progression every full cycle (4 chords) for variation
|
);
|
||||||
if (progressionChangeCounter >= currentProgression.length) {
|
|
||||||
currentChordIndex = (currentChordIndex + 1) % chordProgressions.length;
|
|
||||||
currentProgression = chordProgressions[currentChordIndex];
|
|
||||||
sequence!.events = currentProgression;
|
|
||||||
progressionChangeCounter = 0;
|
|
||||||
}
|
|
||||||
}, currentProgression, "4n");
|
|
||||||
|
|
||||||
sequence.start(0);
|
sequence.start(0);
|
||||||
|
|
||||||
|
// Create arpeggio sequence
|
||||||
|
if (arpSequence) {
|
||||||
|
arpSequence.dispose();
|
||||||
|
arpSequence = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
arpSequence = new Tone.Sequence(
|
||||||
|
(time: number, chord) => {
|
||||||
|
if (arpSynth && chord && chord.notes) {
|
||||||
|
// Play arpeggio pattern through the chord notes
|
||||||
|
chord.notes.forEach((note: string, index: number) => {
|
||||||
|
const noteTime = time + index * 0.15; // 150ms between notes
|
||||||
|
arpSynth!.triggerAttackRelease(note, '16n', noteTime);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentProgression,
|
||||||
|
arpInterval
|
||||||
|
);
|
||||||
|
|
||||||
|
arpSequence.start(0);
|
||||||
|
|
||||||
|
// Create ping sequence (reverse arpeggio - evenly spaced through chord duration)
|
||||||
|
if (pingSequence) {
|
||||||
|
pingSequence.dispose();
|
||||||
|
pingSequence = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a flat array of notes: 4 notes per chord, in reverse order, transposed up 2 octaves
|
||||||
|
const pingNotes: string[] = [];
|
||||||
|
currentProgression.forEach((chord) => {
|
||||||
|
if (chord && chord.notes) {
|
||||||
|
// Get 4 notes in reverse order (last to first, cycling if needed)
|
||||||
|
const reversedNotes = [...chord.notes].reverse();
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const note = reversedNotes[i % reversedNotes.length];
|
||||||
|
const noteName = note.slice(0, -1);
|
||||||
|
const octave = parseInt(note.slice(-1));
|
||||||
|
const highNote = noteName + (octave + 2);
|
||||||
|
pingNotes.push(highNote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pingSequence = new Tone.Sequence(
|
||||||
|
(time: number, note: string) => {
|
||||||
|
if (pingSynth && note) {
|
||||||
|
pingSynth.triggerAttackRelease(note, '32n', time);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pingNotes,
|
||||||
|
pingInterval // Temperature-reactive: 1n (cold) to 16n (hot)
|
||||||
|
);
|
||||||
|
|
||||||
|
pingSequence.start(0);
|
||||||
|
|
||||||
|
// Create bass sequence
|
||||||
|
if (bassSequence) {
|
||||||
|
bassSequence.dispose();
|
||||||
|
bassSequence = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bassSequence = new Tone.Sequence(
|
||||||
|
(time: number, chord) => {
|
||||||
|
if (bassSynth && chord && chord.notes && chord.notes.length > 0) {
|
||||||
|
// Get root note (first note of chord) and transpose down 2 octaves
|
||||||
|
const rootNote = chord.notes[0];
|
||||||
|
const noteName = rootNote.slice(0, -1); // e.g., 'C' from 'C4'
|
||||||
|
const octave = parseInt(rootNote.slice(-1)); // e.g., 4 from 'C4'
|
||||||
|
const bassNote = noteName + (octave - 2); // e.g., 'C2'
|
||||||
|
|
||||||
|
// Randomize release time: half to full chord duration
|
||||||
|
// Quarter note = 1 beat, so random between 0.5 and 1.0 beats
|
||||||
|
const randomRelease = 0.5 + Math.random() * 0.5;
|
||||||
|
bassSynth.envelope.release = randomRelease * (60 / bpm);
|
||||||
|
|
||||||
|
// Trigger bass note
|
||||||
|
bassSynth.triggerAttackRelease(bassNote, '4n', time);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentProgression,
|
||||||
|
'4n'
|
||||||
|
);
|
||||||
|
|
||||||
|
bassSequence.start(0);
|
||||||
|
|
||||||
Tone.getTransport().start();
|
Tone.getTransport().start();
|
||||||
isPlaying = true;
|
isPlaying = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -158,6 +310,21 @@
|
|||||||
sequence.dispose();
|
sequence.dispose();
|
||||||
sequence = null;
|
sequence = null;
|
||||||
}
|
}
|
||||||
|
if (arpSequence) {
|
||||||
|
arpSequence.stop();
|
||||||
|
arpSequence.dispose();
|
||||||
|
arpSequence = null;
|
||||||
|
}
|
||||||
|
if (pingSequence) {
|
||||||
|
pingSequence.stop();
|
||||||
|
pingSequence.dispose();
|
||||||
|
pingSequence = null;
|
||||||
|
}
|
||||||
|
if (bassSequence) {
|
||||||
|
bassSequence.stop();
|
||||||
|
bassSequence.dispose();
|
||||||
|
bassSequence = null;
|
||||||
|
}
|
||||||
Tone.getTransport().stop();
|
Tone.getTransport().stop();
|
||||||
Tone.getTransport().cancel();
|
Tone.getTransport().cancel();
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
@@ -180,8 +347,16 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (phaser && isInitialized) {
|
if (delay && isInitialized) {
|
||||||
phaser.frequency.rampTo(phaserBase, 0.5);
|
delay.delayTime.value = delayTime;
|
||||||
|
delay.feedback.rampTo(delayFeedback, 0.5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (filter && isInitialized) {
|
||||||
|
filter.frequency.rampTo(filterCutoff, 1.0);
|
||||||
|
filter.Q.rampTo(filterResonance, 1.0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,6 +372,18 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (arpSynth && isInitialized) {
|
||||||
|
arpSynth.volume.rampTo(arpVolume, 0.5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (pingSynth && isInitialized) {
|
||||||
|
pingSynth.volume.rampTo(pingVolume, 0.5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
initializeAudio();
|
initializeAudio();
|
||||||
@@ -206,33 +393,74 @@
|
|||||||
if (sequence) {
|
if (sequence) {
|
||||||
sequence.dispose();
|
sequence.dispose();
|
||||||
}
|
}
|
||||||
|
if (arpSequence) {
|
||||||
|
arpSequence.dispose();
|
||||||
|
}
|
||||||
|
if (pingSequence) {
|
||||||
|
pingSequence.dispose();
|
||||||
|
}
|
||||||
|
if (bassSequence) {
|
||||||
|
bassSequence.dispose();
|
||||||
|
}
|
||||||
if (synth) {
|
if (synth) {
|
||||||
synth.dispose();
|
synth.dispose();
|
||||||
}
|
}
|
||||||
|
if (arpSynth) {
|
||||||
|
arpSynth.dispose();
|
||||||
|
}
|
||||||
|
if (pingSynth) {
|
||||||
|
pingSynth.dispose();
|
||||||
|
}
|
||||||
|
if (bassSynth) {
|
||||||
|
bassSynth.dispose();
|
||||||
|
}
|
||||||
if (reverb) {
|
if (reverb) {
|
||||||
reverb.dispose();
|
reverb.dispose();
|
||||||
}
|
}
|
||||||
if (phaser) {
|
if (delay) {
|
||||||
phaser.dispose();
|
delay.dispose();
|
||||||
|
}
|
||||||
|
if (filter) {
|
||||||
|
filter.dispose();
|
||||||
}
|
}
|
||||||
if (gain) {
|
if (gain) {
|
||||||
gain.dispose();
|
gain.dispose();
|
||||||
}
|
}
|
||||||
|
if (analyser) {
|
||||||
|
analyser.dispose();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-[300px_1fr] gap-8 p-4 max-w-6xl mx-auto">
|
||||||
<!-- TODO: ADD VIZUALZ https://www.npmjs.com/package/p5-svelte -->
|
<div class="flex flex-col gap-4">
|
||||||
<!-- https://jsfiddle.net/aqilahmisuary/ztf5a72h/#base -->
|
|
||||||
<div class="controls-container">
|
|
||||||
<!-- Playback Control -->
|
|
||||||
<div class="playback-section">
|
|
||||||
<button
|
<button
|
||||||
class="play-button {isPlaying ? 'playing' : ''}"
|
class="px-6 py-3 text-base cursor-crosshair transition-all duration-300 rounded-md border border-white/20 hover:border-white/40 disabled:opacity-50 disabled:cursor-not-allowed {isPlaying
|
||||||
|
? 'bg-white text-black'
|
||||||
|
: 'bg-transparent text-white'}"
|
||||||
onclick={togglePlayback}
|
onclick={togglePlayback}
|
||||||
disabled={!isInitialized}
|
disabled={!isInitialized}
|
||||||
>
|
>
|
||||||
{isPlaying ? 'Stop' : 'Play'} ✨
|
{isPlaying ? 'Stop' : 'Play'} Weather Ambient
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{#if isPlaying}
|
||||||
|
<div class="flex flex-col gap-2 text-sm opacity-80">
|
||||||
|
<p class="m-0">Temperature: {temperature2m.toFixed(1)}°C</p>
|
||||||
|
<p class="m-0">Humidity: {relativeHumidity2m}%</p>
|
||||||
|
<p class="m-0">Cloud Cover: {cloudCover}%</p>
|
||||||
|
<p class="m-0">Wind Speed: {windSpeed10m.toFixed(1)} m/s</p>
|
||||||
|
<p class="opacity-40 my-1">---</p>
|
||||||
|
<p class="m-0">BPM: {bpm}</p>
|
||||||
|
<p class="m-0">Weather Extremity: {(weatherExtremity * 100).toFixed(0)}%</p>
|
||||||
|
<p class="m-0">Reverb: {reverbWet.toFixed(2)}</p>
|
||||||
|
<p class="m-0">Delay: {delayTime} @ {delayFeedback.toFixed(2)} feedback</p>
|
||||||
|
<p class="m-0">Filter: {Math.round(filterCutoff)}Hz Q:{filterResonance.toFixed(1)}</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<AudioVisualization {isPlaying} {analyser} width={400} height={400} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
let error: GeolocationError | undefined = $state(undefined);
|
let error: GeolocationError | undefined = $state(undefined);
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
enableHighAccuracy: true,
|
enableHighAccuracy: false, // Use WiFi/network location (faster than GPS)
|
||||||
timeout: 5000, // milliseconds
|
timeout: 10000, // milliseconds - increased for better reliability
|
||||||
maximumAge: 60 * 60 * 1000, // milliseconds
|
maximumAge: 0, // Don't use cached position
|
||||||
};
|
};
|
||||||
|
|
||||||
$inspect(error);
|
$inspect(error);
|
||||||
@@ -27,18 +27,18 @@
|
|||||||
|
|
||||||
<div class="flex min-h-screen flex-col items-center justify-center">
|
<div class="flex min-h-screen flex-col items-center justify-center">
|
||||||
{#if !getPosition}
|
{#if !getPosition}
|
||||||
<button class="animate-pulse cursor-crosshair text-xl" onclick={flipGetPosition}>
|
<button class="animate-pulse cursor-crosshair text-xl hover:text-blue-400 transition-colors" onclick={flipGetPosition}>
|
||||||
Let me show you...
|
Let me show you...
|
||||||
</button>
|
</button>
|
||||||
{:else if loading}
|
{:else if loading}
|
||||||
<p class="text-xl">Loading...</p>
|
<p class="text-xl">Loading...</p>
|
||||||
{:else if error}
|
{:else if error}
|
||||||
<p class="text-xl">We can't seem to find you.</p>
|
<p class="text-xl text-red-400">We can't seem to find you.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="mb-5 text-xl">
|
<p class="mb-5 text-xl text-center px-4">
|
||||||
Your Position is set as: {position?.coords?.latitude}, {position?.coords?.longitude}
|
Your Position is set as: {position?.coords?.latitude}, {position?.coords?.longitude}
|
||||||
</p>
|
</p>
|
||||||
<button class="animate-pulse cursor-crosshair text-lg" onclick={goToPlayer}
|
<button class="animate-pulse cursor-crosshair text-lg hover:text-blue-400 transition-colors" onclick={goToPlayer}
|
||||||
>Ok, on-out with it</button
|
>Ok, on-out with it</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import WeatherGen from '$lib/generators/weather/WeatherGen.svelte';
|
import WeatherGen from '$lib/generators/weather/WeatherGen.svelte';
|
||||||
|
import AirQualityGen from '$lib/generators/air-quality/AirQualityGen.svelte';
|
||||||
|
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
|
|
||||||
@@ -7,13 +8,17 @@
|
|||||||
const currentWeather = data.weatherData.current;
|
const currentWeather = data.weatherData.current;
|
||||||
const currentAirQuality = data.airQualityData.current;
|
const currentAirQuality = data.airQualityData.current;
|
||||||
|
|
||||||
|
$inspect(currentWeather);
|
||||||
$inspect(currentWeather)
|
$inspect(currentAirQuality);
|
||||||
$inspect(currentAirQuality)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="min-h-screen p-8">
|
||||||
<div class="flex min-h-screen flex-col items-center justify-center">
|
<div class="max-w-7xl mx-auto space-y-8">
|
||||||
<WeatherGen {...currentWeather}></WeatherGen>
|
<WeatherGen {...currentWeather}></WeatherGen>
|
||||||
|
<AirQualityGen
|
||||||
|
{...currentAirQuality}
|
||||||
|
relativeHumidity2m={currentWeather.relativeHumidity2m}
|
||||||
|
windSpeed10m={currentWeather.windSpeed10m}
|
||||||
|
></AirQualityGen>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const load: PageLoad = async ({ url }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const getWeather = async (long: string, lat: string): Promise<WeatherData> => {
|
const getWeather = async (lat: string, long: string): Promise<WeatherData> => {
|
||||||
const apiParams = {
|
const apiParams = {
|
||||||
"latitude": lat,
|
"latitude": lat,
|
||||||
"longitude": long,
|
"longitude": long,
|
||||||
@@ -45,7 +45,7 @@ const getWeather = async (long: string, lat: string): Promise<WeatherData> => {
|
|||||||
return weatherData
|
return weatherData
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAirQuality = async (long: string, lat: string): Promise<AirQualityData> => {
|
const getAirQuality = async (lat: string, long: string): Promise<AirQualityData> => {
|
||||||
const params = {
|
const params = {
|
||||||
"latitude": lat,
|
"latitude": lat,
|
||||||
"longitude": long,
|
"longitude": long,
|
||||||
|
|||||||
Reference in New Issue
Block a user