From c3f27d1ad958d928ce4b64d5dbb38eb292179913 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Wed, 30 Aug 2023 14:32:20 +0200 Subject: [PATCH 01/17] Boilerplate for ZZFX Synth support --- packages/superdough/helpers.mjs | 23 + packages/superdough/package-lock.json | 577 ++++++++++++++++++++++++++ packages/superdough/package.json | 3 +- packages/superdough/synth.mjs | 42 ++ pnpm-lock.yaml | 11 + 5 files changed, 655 insertions(+), 1 deletion(-) create mode 100644 packages/superdough/package-lock.json diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 7cc54c8d..622d3a38 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -1,4 +1,5 @@ import { getAudioContext } from './superdough.mjs'; +import * as zzfx from 'zzfx'; export function gainNode(value) { const node = getAudioContext().createGain(); @@ -6,6 +7,28 @@ export function gainNode(value) { return node; } +export const getZZFX = ({ wave, freq, t, params}) => { + params.frequency = freq; + params.shape = { + 'zsine': 0, + 'ztri': 1, + 'zsaw': 2, + 'ztan': 3, + 'znoise': 4, + }[wave]; + const samples = zzfx.buildSamples(...params); + const context = getAudioContext(); + const buffer = context.createBuffer( + samples.length, + samples[0].length, + context.sampleRate + ); + const source = context.createBufferSource(); + return { + node: source, + stop: (time) => source.stop(time), buffer} +} + export const getOscillator = ({ s, freq, t }) => { // make oscillator const o = getAudioContext().createOscillator(); diff --git a/packages/superdough/package-lock.json b/packages/superdough/package-lock.json new file mode 100644 index 00000000..f805d3e8 --- /dev/null +++ b/packages/superdough/package-lock.json @@ -0,0 +1,577 @@ +{ + "name": "superdough", + "version": "0.9.5", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "superdough", + "version": "0.9.5", + "license": "AGPL-3.0-or-later", + "dependencies": { + "nanostores": "^0.8.1", + "superdough": "^0.9.5" + }, + "devDependencies": { + "vite": "^4.3.3" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanostores": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.8.1.tgz", + "integrity": "sha512-1ZCfQtII2XeFDrtqXL2cdQ/diGrLxzRB3YMyQjn8m7GSGQrJfGST2iuqMpWnS/ZlifhtjgR/SX0Jy6Uij6lRLA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "engines": { + "node": "^16.0.0 || >=18.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.4.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", + "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", + "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superdough": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/superdough/-/superdough-0.9.5.tgz", + "integrity": "sha512-PcEzWaD9oQ6apDCLmQq9OtyiAVB+Ell4U3RsYTR48L6jTssZILfYEG8pzHVf06QHeCl0rhl3/QlgO5Vf9HKMkg==", + "dependencies": { + "nanostores": "^0.8.1" + } + }, + "node_modules/vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/packages/superdough/package.json b/packages/superdough/package.json index d82448d6..159ea21d 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -36,6 +36,7 @@ "vite": "^4.3.3" }, "dependencies": { - "nanostores": "^0.8.1" + "nanostores": "^0.8.1", + "zzfx": "^1.2.0" } } diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 57317133..9d3b2b0b 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -21,6 +21,48 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => { }; export function registerSynthSounds() { + ['zsine', 'zsaw', 'ztri', 'ztan', 'znoise'].forEach((wave) => { + registerSound( + wave, + (t, value, onended) => { + const { + attack = 0.001, + decay = 0.05, + sustain = 0.6, + release = 0.01, + } = value; + let { n, note, freq } = value; + n = note || n || 36; + if (typeof n === 'string') { + n = noteToMidi(n); // e.g. c3 => 48 + } + // get frequency + if (!freq && typeof n === 'number') { + freq = midiToFreq(n); // + 48); + } + const params = {}; + const { node: o, stop } = getZZFX({ wave, freq, t, params }); + const g = gainNode(0.3); + // envelope + const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t); + o.onended = () => { + o.disconnect(); + g.disconnect(); + onended(); + }; + console.log(o) + return { + node: o.connect(g).connect(envelope), + stop: (releaseTime) => { + releaseEnvelope(releaseTime); + let end = releaseTime + release; + stop(end); + }, + }; + } + ) + }); + ['sine', 'square', 'triangle', 'sawtooth'].forEach((wave) => { registerSound( wave, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 643a4766..e328a467 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -391,6 +395,9 @@ importers: nanostores: specifier: ^0.8.1 version: 0.8.1 + zzfx: + specifier: ^1.2.0 + version: 1.2.0 devDependencies: vite: specifier: ^4.3.3 @@ -14030,3 +14037,7 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + + /zzfx@1.2.0: + resolution: {integrity: sha512-RtFz6PTMfCmxTfaCOv6GWAV4YaL/T0hltiMGkd87clybO8WLPlH6kX8sNkZGFKw9YPyu1UNsUYf/5/Vn4dondA==} + dev: false From afb8ab44cf4c273a6478a4f7f79545f6bb780c7d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 30 Aug 2023 15:25:23 +0200 Subject: [PATCH 02/17] more zzfx wiring --- packages/core/controls.mjs | 23 ++++++- packages/superdough/helpers.mjs | 110 ++++++++++++++++++++++++++------ packages/superdough/synth.mjs | 51 ++++----------- 3 files changed, 124 insertions(+), 60 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 717a8353..6a7e6b4b 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -802,8 +802,29 @@ const generic_params = [ * */ ['clip'], -]; + // ZZFX + //['volume'], + ['randomness'], + // ['frequency'], => freq + /* ['attack'], + ['decay'], + ['sustain'], + ['release'], */ + // ['shape'], // duplicate + ['shapeCurve'], + // ['slide'], // superdirt duplicate + ['deltaSlide'], + ['pitchJump'], + ['pitchJumpTime'], + ['repeatTime'], + ['noise'], + ['modulation'], + ['bitCrush'], // like crush.. + //['delay'], // duplicate + // ['sustainVolume'], // not needed? + ['tremolo'], +]; // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 controls.createParam = function (names) { diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 622d3a38..ddd94fca 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -1,5 +1,5 @@ import { getAudioContext } from './superdough.mjs'; -import * as zzfx from 'zzfx'; +import { ZZFX } from 'zzfx'; export function gainNode(value) { const node = getAudioContext().createGain(); @@ -7,27 +7,97 @@ export function gainNode(value) { return node; } -export const getZZFX = ({ wave, freq, t, params}) => { - params.frequency = freq; - params.shape = { - 'zsine': 0, - 'ztri': 1, - 'zsaw': 2, - 'ztan': 3, - 'znoise': 4, - }[wave]; - const samples = zzfx.buildSamples(...params); +export const getZZFX = (value, t, duration) => { + let { + s, + note = 36, + freq, + // + randomness = 0, + attack = 0, + decay = 0, + sustain = 0.8, + release = 0.1, + shapeCurve = 1, + slide = 0, + deltaSlide = 0, + pitchJump = 0, + pitchJumpTime = 0, + repeatTime = 0, + noise = 0, + modulation = 0, + bitCrush = 0, + delay = 0, + tremolo = 0, + } = value; + if (typeof note === 'string') { + note = noteToMidi(note); // e.g. c3 => 48 + } + // get frequency + if (!freq && typeof n === 'number') { + freq = midiToFreq(n); + } + const shape = ['zsine', 'ztri', 'zsaw', 'ztan', 'znoise'].indexOf(s) || 0; + + const params = [ + 1, // volume + randomness, // randomness + freq, + attack, + duration, // sustain time + release, + shape, + shapeCurve, + slide, + deltaSlide, + pitchJump, + pitchJumpTime, + repeatTime, + noise, + modulation, + bitCrush, + delay, + sustain, // sustain volume! + decay, + tremolo, + ]; + const paramOrder = [ + 'volume', + 'randomness', + 'frequency', + 'attack', + 'sustain', + 'release', + 'shape', + 'shapeCurve', + 'slide', + 'deltaSlide', + 'pitchJump', + 'pitchJumpTime', + 'repeatTime', + 'noise', + 'modulation', + 'bitCrush', + 'delay', + 'sustainVolume', + 'decay', + 'tremolo', + ]; + + const readableParams = Object.fromEntries(paramOrder.map((param, i) => [param, params[i]])); + console.log(readableParams); + + const samples = ZZFX.buildSamples(...params); const context = getAudioContext(); - const buffer = context.createBuffer( - samples.length, - samples[0].length, - context.sampleRate - ); - const source = context.createBufferSource(); + const buffer = context.createBuffer(1, samples.length, context.sampleRate); + buffer.getChannelData(0).set(samples); + const source = getAudioContext().createBufferSource(); + source.buffer = buffer; + source.start(t); return { - node: source, - stop: (time) => source.stop(time), buffer} -} + node: source, + }; +}; export const getOscillator = ({ s, freq, t }) => { // make oscillator diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 9d3b2b0b..02cf4c8a 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -22,45 +22,18 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => { export function registerSynthSounds() { ['zsine', 'zsaw', 'ztri', 'ztan', 'znoise'].forEach((wave) => { - registerSound( - wave, - (t, value, onended) => { - const { - attack = 0.001, - decay = 0.05, - sustain = 0.6, - release = 0.01, - } = value; - let { n, note, freq } = value; - n = note || n || 36; - if (typeof n === 'string') { - n = noteToMidi(n); // e.g. c3 => 48 - } - // get frequency - if (!freq && typeof n === 'number') { - freq = midiToFreq(n); // + 48); - } - const params = {}; - const { node: o, stop } = getZZFX({ wave, freq, t, params }); - const g = gainNode(0.3); - // envelope - const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t); - o.onended = () => { - o.disconnect(); - g.disconnect(); - onended(); - }; - console.log(o) - return { - node: o.connect(g).connect(envelope), - stop: (releaseTime) => { - releaseEnvelope(releaseTime); - let end = releaseTime + release; - stop(end); - }, - }; - } - ) + registerSound(wave, (t, value, onended) => { + const duration = 0.2; + const { node: o } = getZZFX({ s: wave, ...value }, t, duration); + o.onended = () => { + o.disconnect(); + onended(); + }; + return { + node: o, + stop: () => {}, + }; + }); }); ['sine', 'square', 'triangle', 'sawtooth'].forEach((wave) => { From 1463f27a0f4cc9f5f40b1d9c49273764180914d0 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 30 Aug 2023 15:27:07 +0200 Subject: [PATCH 03/17] move zzfx stuff to separate file --- packages/react/examples/nano-repl/src/App.jsx | 3 +- packages/superdough/helpers.mjs | 93 --------------- packages/superdough/index.mjs | 1 + packages/superdough/zzfx.mjs | 110 ++++++++++++++++++ 4 files changed, 113 insertions(+), 94 deletions(-) create mode 100644 packages/superdough/zzfx.mjs diff --git a/packages/react/examples/nano-repl/src/App.jsx b/packages/react/examples/nano-repl/src/App.jsx index 086923ac..f84356c7 100644 --- a/packages/react/examples/nano-repl/src/App.jsx +++ b/packages/react/examples/nano-repl/src/App.jsx @@ -6,6 +6,7 @@ import { panic, webaudioOutput, registerSynthSounds, + registerZZFXSounds, } from '@strudel.cycles/webaudio'; import { registerSoundfonts } from '@strudel.cycles/soundfonts'; import { useCallback, useState } from 'react'; @@ -26,7 +27,7 @@ async function init() { import('@strudel.cycles/osc'), ); - await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]); + await Promise.all([loadModules, registerSynthSounds(), registerZZFXSounds(), registerSoundfonts()]); } init(); diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index ddd94fca..7cc54c8d 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -1,5 +1,4 @@ import { getAudioContext } from './superdough.mjs'; -import { ZZFX } from 'zzfx'; export function gainNode(value) { const node = getAudioContext().createGain(); @@ -7,98 +6,6 @@ export function gainNode(value) { return node; } -export const getZZFX = (value, t, duration) => { - let { - s, - note = 36, - freq, - // - randomness = 0, - attack = 0, - decay = 0, - sustain = 0.8, - release = 0.1, - shapeCurve = 1, - slide = 0, - deltaSlide = 0, - pitchJump = 0, - pitchJumpTime = 0, - repeatTime = 0, - noise = 0, - modulation = 0, - bitCrush = 0, - delay = 0, - tremolo = 0, - } = value; - if (typeof note === 'string') { - note = noteToMidi(note); // e.g. c3 => 48 - } - // get frequency - if (!freq && typeof n === 'number') { - freq = midiToFreq(n); - } - const shape = ['zsine', 'ztri', 'zsaw', 'ztan', 'znoise'].indexOf(s) || 0; - - const params = [ - 1, // volume - randomness, // randomness - freq, - attack, - duration, // sustain time - release, - shape, - shapeCurve, - slide, - deltaSlide, - pitchJump, - pitchJumpTime, - repeatTime, - noise, - modulation, - bitCrush, - delay, - sustain, // sustain volume! - decay, - tremolo, - ]; - const paramOrder = [ - 'volume', - 'randomness', - 'frequency', - 'attack', - 'sustain', - 'release', - 'shape', - 'shapeCurve', - 'slide', - 'deltaSlide', - 'pitchJump', - 'pitchJumpTime', - 'repeatTime', - 'noise', - 'modulation', - 'bitCrush', - 'delay', - 'sustainVolume', - 'decay', - 'tremolo', - ]; - - const readableParams = Object.fromEntries(paramOrder.map((param, i) => [param, params[i]])); - console.log(readableParams); - - const samples = ZZFX.buildSamples(...params); - const context = getAudioContext(); - const buffer = context.createBuffer(1, samples.length, context.sampleRate); - buffer.getChannelData(0).set(samples); - const source = getAudioContext().createBufferSource(); - source.buffer = buffer; - source.start(t); - return { - node: source, - }; -}; - export const getOscillator = ({ s, freq, t }) => { // make oscillator const o = getAudioContext().createOscillator(); diff --git a/packages/superdough/index.mjs b/packages/superdough/index.mjs index b795539a..e5d4498b 100644 --- a/packages/superdough/index.mjs +++ b/packages/superdough/index.mjs @@ -8,4 +8,5 @@ export * from './superdough.mjs'; export * from './sampler.mjs'; export * from './helpers.mjs'; export * from './synth.mjs'; +export * from './zzfx.mjs'; export * from './logger.mjs'; diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs new file mode 100644 index 00000000..a8ddf30b --- /dev/null +++ b/packages/superdough/zzfx.mjs @@ -0,0 +1,110 @@ +import { ZZFX } from 'zzfx'; + +export const getZZFX = (value, t, duration) => { + let { + s, + note = 36, + freq, + // + randomness = 0, + attack = 0, + decay = 0, + sustain = 0.8, + release = 0.1, + shapeCurve = 1, + slide = 0, + deltaSlide = 0, + pitchJump = 0, + pitchJumpTime = 0, + repeatTime = 0, + noise = 0, + modulation = 0, + bitCrush = 0, + delay = 0, + tremolo = 0, + } = value; + if (typeof note === 'string') { + note = noteToMidi(note); // e.g. c3 => 48 + } + // get frequency + if (!freq && typeof n === 'number') { + freq = midiToFreq(n); + } + const shape = ['zsine', 'ztri', 'zsaw', 'ztan', 'znoise'].indexOf(s) || 0; + + const params = [ + 1, // volume + randomness, // randomness + freq, + attack, + duration, // sustain time + release, + shape, + shapeCurve, + slide, + deltaSlide, + pitchJump, + pitchJumpTime, + repeatTime, + noise, + modulation, + bitCrush, + delay, + sustain, // sustain volume! + decay, + tremolo, + ]; + const paramOrder = [ + 'volume', + 'randomness', + 'frequency', + 'attack', + 'sustain', + 'release', + 'shape', + 'shapeCurve', + 'slide', + 'deltaSlide', + 'pitchJump', + 'pitchJumpTime', + 'repeatTime', + 'noise', + 'modulation', + 'bitCrush', + 'delay', + 'sustainVolume', + 'decay', + 'tremolo', + ]; + + const readableParams = Object.fromEntries(paramOrder.map((param, i) => [param, params[i]])); + console.log(readableParams); + + const samples = ZZFX.buildSamples(...params); + const context = getAudioContext(); + const buffer = context.createBuffer(1, samples.length, context.sampleRate); + buffer.getChannelData(0).set(samples); + const source = getAudioContext().createBufferSource(); + source.buffer = buffer; + source.start(t); + return { + node: source, + }; +}; + +export function registerZZFXSounds() { + ['zsine', 'zsaw', 'ztri', 'ztan', 'znoise'].forEach((wave) => { + registerSound(wave, (t, value, onended) => { + const duration = 0.2; + const { node: o } = getZZFX({ s: wave, ...value }, t, duration); + o.onended = () => { + o.disconnect(); + onended(); + }; + return { + node: o, + stop: () => {}, + }; + }); + }); +} From 90cdea3656f16ddcc27117f4f74fe6426009ecc2 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 30 Aug 2023 15:32:45 +0200 Subject: [PATCH 04/17] fix: double registering --- packages/react/examples/nano-repl/src/App.jsx | 3 +-- packages/superdough/synth.mjs | 15 --------------- packages/superdough/zzfx.mjs | 7 +++++-- website/src/repl/prebake.mjs | 3 ++- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/packages/react/examples/nano-repl/src/App.jsx b/packages/react/examples/nano-repl/src/App.jsx index f84356c7..086923ac 100644 --- a/packages/react/examples/nano-repl/src/App.jsx +++ b/packages/react/examples/nano-repl/src/App.jsx @@ -6,7 +6,6 @@ import { panic, webaudioOutput, registerSynthSounds, - registerZZFXSounds, } from '@strudel.cycles/webaudio'; import { registerSoundfonts } from '@strudel.cycles/soundfonts'; import { useCallback, useState } from 'react'; @@ -27,7 +26,7 @@ async function init() { import('@strudel.cycles/osc'), ); - await Promise.all([loadModules, registerSynthSounds(), registerZZFXSounds(), registerSoundfonts()]); + await Promise.all([loadModules, registerSynthSounds(), registerSoundfonts()]); } init(); diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 02cf4c8a..57317133 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -21,21 +21,6 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => { }; export function registerSynthSounds() { - ['zsine', 'zsaw', 'ztri', 'ztan', 'znoise'].forEach((wave) => { - registerSound(wave, (t, value, onended) => { - const duration = 0.2; - const { node: o } = getZZFX({ s: wave, ...value }, t, duration); - o.onended = () => { - o.disconnect(); - onended(); - }; - return { - node: o, - stop: () => {}, - }; - }); - }); - ['sine', 'square', 'triangle', 'sawtooth'].forEach((wave) => { registerSound( wave, diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs index a8ddf30b..352a88a2 100644 --- a/packages/superdough/zzfx.mjs +++ b/packages/superdough/zzfx.mjs @@ -1,4 +1,6 @@ import { ZZFX } from 'zzfx'; +import { midiToFreq, noteToMidi } from './util.mjs'; +import { registerSound, getAudioContext } from './superdough.mjs'; export const getZZFX = (value, t, duration) => { let { @@ -27,8 +29,8 @@ export const getZZFX = (value, t, duration) => { note = noteToMidi(note); // e.g. c3 => 48 } // get frequency - if (!freq && typeof n === 'number') { - freq = midiToFreq(n); + if (!freq && typeof note === 'number') { + freq = midiToFreq(note); } const shape = ['zsine', 'ztri', 'zsaw', 'ztan', 'znoise'].indexOf(s) || 0; @@ -93,6 +95,7 @@ export const getZZFX = (value, t, duration) => { }; export function registerZZFXSounds() { + console.log('registerZZFXSounds'); ['zsine', 'zsaw', 'ztri', 'ztan', 'znoise'].forEach((wave) => { registerSound(wave, (t, value, onended) => { const duration = 0.2; diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index 6bdc5c0d..28678c9d 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -1,5 +1,5 @@ import { Pattern, noteToMidi, valueToMidi } from '@strudel.cycles/core'; -import { registerSynthSounds, samples } from '@strudel.cycles/webaudio'; +import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio'; import './piano.mjs'; import './files.mjs'; @@ -8,6 +8,7 @@ export async function prebake() { // License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm await Promise.all([ registerSynthSounds(), + registerZZFXSounds(), //registerSoundfonts(), // need dynamic import here, because importing @strudel.cycles/soundfonts fails on server: // => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically From b991cfc493756fe1aa330c2187d86e9fe301931d Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 03:50:38 +0200 Subject: [PATCH 05/17] fix: use custom version of ZZFX - ZZFX audio context broke tests - only needed buildSamples function --- packages/superdough/README.md | 1 + packages/superdough/package.json | 3 +- packages/superdough/zzfx.mjs | 125 ++++++++++++++++++++++++++++++- pnpm-lock.yaml | 13 +--- 4 files changed, 127 insertions(+), 15 deletions(-) diff --git a/packages/superdough/README.md b/packages/superdough/README.md index 4d670ac6..ac46f69e 100644 --- a/packages/superdough/README.md +++ b/packages/superdough/README.md @@ -161,5 +161,6 @@ Then just make sure your first call of `superdough` happens after a click of som ## Credits +- [ZZFX](https://github.com/KilledByAPixel/ZzFX) used for synths starting with z - [SuperDirt](https://github.com/musikinformatik/SuperDirt) - [WebDirt](https://github.com/dktr0/WebDirt) diff --git a/packages/superdough/package.json b/packages/superdough/package.json index 159ea21d..d82448d6 100644 --- a/packages/superdough/package.json +++ b/packages/superdough/package.json @@ -36,7 +36,6 @@ "vite": "^4.3.3" }, "dependencies": { - "nanostores": "^0.8.1", - "zzfx": "^1.2.0" + "nanostores": "^0.8.1" } } diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs index 352a88a2..4e6774bd 100644 --- a/packages/superdough/zzfx.mjs +++ b/packages/superdough/zzfx.mjs @@ -1,4 +1,4 @@ -import { ZZFX } from 'zzfx'; +//import { ZZFX } from 'zzfx'; import { midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext } from './superdough.mjs'; @@ -82,7 +82,7 @@ export const getZZFX = (value, t, duration) => { const readableParams = Object.fromEntries(paramOrder.map((param, i) => [param, params[i]])); console.log(readableParams); - const samples = ZZFX.buildSamples(...params); + const samples = /* ZZFX. */ buildSamples(...params); const context = getAudioContext(); const buffer = context.createBuffer(1, samples.length, context.sampleRate); buffer.getChannelData(0).set(samples); @@ -98,7 +98,7 @@ export function registerZZFXSounds() { console.log('registerZZFXSounds'); ['zsine', 'zsaw', 'ztri', 'ztan', 'znoise'].forEach((wave) => { registerSound(wave, (t, value, onended) => { - const duration = 0.2; + const duration = 0.3; const { node: o } = getZZFX({ s: wave, ...value }, t, duration); o.onended = () => { o.disconnect(); @@ -111,3 +111,122 @@ export function registerZZFXSounds() { }); }); } + +// https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6 +// changes: replaced this.volume with 1 + using sampleRate from getAudioContext() +function buildSamples( + volume = 1, + randomness = 0.05, + frequency = 220, + attack = 0, + sustain = 0, + release = 0.1, + shape = 0, + shapeCurve = 1, + slide = 0, + deltaSlide = 0, + pitchJump = 0, + pitchJumpTime = 0, + repeatTime = 0, + noise = 0, + modulation = 0, + bitCrush = 0, + delay = 0, + sustainVolume = 1, + decay = 0, + tremolo = 0, +) { + // init parameters + let PI2 = Math.PI * 2, + sampleRate = getAudioContext().sampleRate, + sign = (v) => (v > 0 ? 1 : -1), + startSlide = (slide *= (500 * PI2) / sampleRate / sampleRate), + startFrequency = (frequency *= ((1 + randomness * 2 * Math.random() - randomness) * PI2) / sampleRate), + b = [], + t = 0, + tm = 0, + i = 0, + j = 1, + r = 0, + c = 0, + s = 0, + f, + length; + + // scale by sample rate + attack = attack * sampleRate + 9; // minimum attack to prevent pop + decay *= sampleRate; + sustain *= sampleRate; + release *= sampleRate; + delay *= sampleRate; + deltaSlide *= (500 * PI2) / sampleRate ** 3; + modulation *= PI2 / sampleRate; + pitchJump *= PI2 / sampleRate; + pitchJumpTime *= sampleRate; + repeatTime = (repeatTime * sampleRate) | 0; + + // generate waveform + for (length = (attack + decay + sustain + release + delay) | 0; i < length; b[i++] = s) { + if (!(++c % ((bitCrush * 100) | 0))) { + // bit crush + s = shape + ? shape > 1 + ? shape > 2 + ? shape > 3 // wave shape + ? Math.sin((t % PI2) ** 3) // 4 noise + : Math.max(Math.min(Math.tan(t), 1), -1) // 3 tan + : 1 - (((((2 * t) / PI2) % 2) + 2) % 2) // 2 saw + : 1 - 4 * Math.abs(Math.round(t / PI2) - t / PI2) // 1 triangle + : Math.sin(t); // 0 sin + + s = + (repeatTime + ? 1 - tremolo + tremolo * Math.sin((PI2 * i) / repeatTime) // tremolo + : 1) * + sign(s) * + Math.abs(s) ** shapeCurve * // curve 0=square, 2=pointy + volume * + 1 * // envelope + (i < attack + ? i / attack // attack + : i < attack + decay // decay + ? 1 - ((i - attack) / decay) * (1 - sustainVolume) // decay falloff + : i < attack + decay + sustain // sustain + ? sustainVolume // sustain volume + : i < length - delay // release + ? ((length - i - delay) / release) * // release falloff + sustainVolume // release volume + : 0); // post release + + s = delay + ? s / 2 + + (delay > i + ? 0 // delay + : ((i < length - delay ? 1 : (length - i) / delay) * // release delay + b[(i - delay) | 0]) / + 2) + : s; // sample delay + } + + f = + (frequency += slide += deltaSlide) * // frequency + Math.cos(modulation * tm++); // modulation + t += f - f * noise * (1 - (((Math.sin(i) + 1) * 1e9) % 2)); // noise + + if (j && ++j > pitchJumpTime) { + // pitch jump + frequency += pitchJump; // apply pitch jump + startFrequency += pitchJump; // also apply to start + j = 0; // stop pitch jump time + } + + if (repeatTime && !(++r % repeatTime)) { + // repeat + frequency = startFrequency; // reset frequency + slide = startSlide; // reset slide + j ||= 1; // reset pitch jump time + } + } + + return b; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e328a467..25b744d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - importers: .: @@ -395,9 +391,6 @@ importers: nanostores: specifier: ^0.8.1 version: 0.8.1 - zzfx: - specifier: ^1.2.0 - version: 1.2.0 devDependencies: vite: specifier: ^4.3.3 @@ -14038,6 +14031,6 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - /zzfx@1.2.0: - resolution: {integrity: sha512-RtFz6PTMfCmxTfaCOv6GWAV4YaL/T0hltiMGkd87clybO8WLPlH6kX8sNkZGFKw9YPyu1UNsUYf/5/Vn4dondA==} - dev: false +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false From 213b1a2daea682f8eaf726b90f7a88c9c1b743ab Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 03:53:52 +0200 Subject: [PATCH 06/17] pull out debugging fn --- packages/superdough/zzfx.mjs | 53 +++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs index 4e6774bd..0707b14b 100644 --- a/packages/superdough/zzfx.mjs +++ b/packages/superdough/zzfx.mjs @@ -56,31 +56,7 @@ export const getZZFX = (value, t, duration) => { decay, tremolo, ]; - const paramOrder = [ - 'volume', - 'randomness', - 'frequency', - 'attack', - 'sustain', - 'release', - 'shape', - 'shapeCurve', - 'slide', - 'deltaSlide', - 'pitchJump', - 'pitchJumpTime', - 'repeatTime', - 'noise', - 'modulation', - 'bitCrush', - 'delay', - 'sustainVolume', - 'decay', - 'tremolo', - ]; - - const readableParams = Object.fromEntries(paramOrder.map((param, i) => [param, params[i]])); - console.log(readableParams); + // console.log(redableZZFX(params)); const samples = /* ZZFX. */ buildSamples(...params); const context = getAudioContext(); @@ -112,6 +88,33 @@ export function registerZZFXSounds() { }); } +// just for debugging +function redableZZFX(params) { + const paramOrder = [ + 'volume', + 'randomness', + 'frequency', + 'attack', + 'sustain', + 'release', + 'shape', + 'shapeCurve', + 'slide', + 'deltaSlide', + 'pitchJump', + 'pitchJumpTime', + 'repeatTime', + 'noise', + 'modulation', + 'bitCrush', + 'delay', + 'sustainVolume', + 'decay', + 'tremolo', + ]; + return Object.fromEntries(paramOrder.map((param, i) => [param, params[i]])); +} + // https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6 // changes: replaced this.volume with 1 + using sampleRate from getAudioContext() function buildSamples( From 1bade7bee83654b18d5eac848c0562e317729925 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 05:23:08 +0200 Subject: [PATCH 07/17] urgh npm --- packages/superdough/package-lock.json | 577 -------------------------- 1 file changed, 577 deletions(-) delete mode 100644 packages/superdough/package-lock.json diff --git a/packages/superdough/package-lock.json b/packages/superdough/package-lock.json deleted file mode 100644 index f805d3e8..00000000 --- a/packages/superdough/package-lock.json +++ /dev/null @@ -1,577 +0,0 @@ -{ - "name": "superdough", - "version": "0.9.5", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "superdough", - "version": "0.9.5", - "license": "AGPL-3.0-or-later", - "dependencies": { - "nanostores": "^0.8.1", - "superdough": "^0.9.5" - }, - "devDependencies": { - "vite": "^4.3.3" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanostores": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.8.1.tgz", - "integrity": "sha512-1ZCfQtII2XeFDrtqXL2cdQ/diGrLxzRB3YMyQjn8m7GSGQrJfGST2iuqMpWnS/ZlifhtjgR/SX0Jy6Uij6lRLA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "engines": { - "node": "^16.0.0 || >=18.0.0" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rollup": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", - "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/superdough": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/superdough/-/superdough-0.9.5.tgz", - "integrity": "sha512-PcEzWaD9oQ6apDCLmQq9OtyiAVB+Ell4U3RsYTR48L6jTssZILfYEG8pzHVf06QHeCl0rhl3/QlgO5Vf9HKMkg==", - "dependencies": { - "nanostores": "^0.8.1" - } - }, - "node_modules/vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", - "dev": true, - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - } - } -} From a786c642f1b0dd71ab772816490fd267413876ce Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 05:25:18 +0200 Subject: [PATCH 08/17] remove weird flags --- pnpm-lock.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 25b744d6..643a4766 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14030,7 +14030,3 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false From 0f54bb19387f3992a69e0365edb9c6c537fd0fd1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 06:57:08 +0200 Subject: [PATCH 09/17] rename some params + use correct duration --- packages/core/controls.mjs | 18 +++------ packages/superdough/superdough.mjs | 2 + packages/superdough/zzfx.mjs | 60 ++++++++++++++++-------------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 6a7e6b4b..109fda87 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -804,25 +804,17 @@ const generic_params = [ ['clip'], // ZZFX - //['volume'], - ['randomness'], - // ['frequency'], => freq - /* ['attack'], - ['decay'], - ['sustain'], - ['release'], */ - // ['shape'], // duplicate + ['zrand'], ['shapeCurve'], - // ['slide'], // superdirt duplicate + ['slide'], // superdirt duplicate ['deltaSlide'], ['pitchJump'], ['pitchJumpTime'], ['repeatTime'], ['noise'], - ['modulation'], - ['bitCrush'], // like crush.. - //['delay'], // duplicate - // ['sustainVolume'], // not needed? + ['zmod'], + ['zcrush'], // like crush.. + ['zdelay'], // duplicate ['tremolo'], ]; // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index d5a2ca3f..af22355f 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -167,6 +167,8 @@ export const superdough = async (value, deadline, hapDuration) => { ); } + // duration is passed as value too.. + value.duration = hapDuration; // calculate absolute time let t = ac.currentTime + deadline; // destructure diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs index 0707b14b..4ec77632 100644 --- a/packages/superdough/zzfx.mjs +++ b/packages/superdough/zzfx.mjs @@ -2,13 +2,13 @@ import { midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext } from './superdough.mjs'; -export const getZZFX = (value, t, duration) => { +export const getZZFX = (value, t) => { let { s, note = 36, freq, // - randomness = 0, + zrand = 0, attack = 0, decay = 0, sustain = 0.8, @@ -21,10 +21,12 @@ export const getZZFX = (value, t, duration) => { repeatTime = 0, noise = 0, modulation = 0, - bitCrush = 0, - delay = 0, + zcrush = 0, + zdelay = 0, tremolo = 0, + duration = 0.2, } = value; + const sustainTime = Math.max(duration - attack - decay, 0); if (typeof note === 'string') { note = noteToMidi(note); // e.g. c3 => 48 } @@ -32,14 +34,16 @@ export const getZZFX = (value, t, duration) => { if (!freq && typeof note === 'number') { freq = midiToFreq(note); } - const shape = ['zsine', 'ztri', 'zsaw', 'ztan', 'znoise'].indexOf(s) || 0; + s = s.replace('z_', ''); + const shape = ['sine', 'triangle', 'sawtooth', 'tan', 'noise'].indexOf(s) || 0; + shapeCurve = s === 'square' ? 0 : shapeCurve; const params = [ - 1, // volume - randomness, // randomness + 0.25, // volume + zrand, freq, attack, - duration, // sustain time + sustainTime, release, shape, shapeCurve, @@ -50,8 +54,8 @@ export const getZZFX = (value, t, duration) => { repeatTime, noise, modulation, - bitCrush, - delay, + zcrush, + zdelay, sustain, // sustain volume! decay, tremolo, @@ -71,20 +75,22 @@ export const getZZFX = (value, t, duration) => { }; export function registerZZFXSounds() { - console.log('registerZZFXSounds'); - ['zsine', 'zsaw', 'ztri', 'ztan', 'znoise'].forEach((wave) => { - registerSound(wave, (t, value, onended) => { - const duration = 0.3; - const { node: o } = getZZFX({ s: wave, ...value }, t, duration); - o.onended = () => { - o.disconnect(); - onended(); - }; - return { - node: o, - stop: () => {}, - }; - }); + ['z_sine', 'z_sawtooth', 'z_triangle', 'z_square', 'z_tan', 'z_noise'].forEach((wave) => { + registerSound( + wave, + (t, value, onended) => { + const { node: o } = getZZFX({ s: wave, ...value }, t); + o.onended = () => { + o.disconnect(); + onended(); + }; + return { + node: o, + stop: () => {}, + }; + }, + { type: 'synth', prebake: true }, + ); }); } @@ -92,7 +98,7 @@ export function registerZZFXSounds() { function redableZZFX(params) { const paramOrder = [ 'volume', - 'randomness', + 'zrand', 'frequency', 'attack', 'sustain', @@ -106,8 +112,8 @@ function redableZZFX(params) { 'repeatTime', 'noise', 'modulation', - 'bitCrush', - 'delay', + 'zcrush', + 'zdelay', 'sustainVolume', 'decay', 'tremolo', From 1241911ec0098cd10d7847f64e2e5afe7a2599a4 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 07:53:41 +0200 Subject: [PATCH 10/17] rename more stuff + pull out fork --- packages/core/controls.mjs | 2 +- packages/superdough/zzfx.mjs | 134 ++---------------------------- packages/superdough/zzfx_fork.mjs | 118 ++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 127 deletions(-) create mode 100644 packages/superdough/zzfx_fork.mjs diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 109fda87..47d77253 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -805,7 +805,7 @@ const generic_params = [ // ZZFX ['zrand'], - ['shapeCurve'], + ['curve'], ['slide'], // superdirt duplicate ['deltaSlide'], ['pitchJump'], diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs index 4ec77632..c57901fb 100644 --- a/packages/superdough/zzfx.mjs +++ b/packages/superdough/zzfx.mjs @@ -1,6 +1,7 @@ //import { ZZFX } from 'zzfx'; import { midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext } from './superdough.mjs'; +import { buildSamples } from './zzfx_fork.mjs'; export const getZZFX = (value, t) => { let { @@ -13,14 +14,14 @@ export const getZZFX = (value, t) => { decay = 0, sustain = 0.8, release = 0.1, - shapeCurve = 1, + curve = 1, slide = 0, deltaSlide = 0, pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, - modulation = 0, + zmod = 0, zcrush = 0, zdelay = 0, tremolo = 0, @@ -36,7 +37,7 @@ export const getZZFX = (value, t) => { } s = s.replace('z_', ''); const shape = ['sine', 'triangle', 'sawtooth', 'tan', 'noise'].indexOf(s) || 0; - shapeCurve = s === 'square' ? 0 : shapeCurve; + curve = s === 'square' ? 0 : curve; const params = [ 0.25, // volume @@ -46,14 +47,14 @@ export const getZZFX = (value, t) => { sustainTime, release, shape, - shapeCurve, + curve, slide, deltaSlide, pitchJump, pitchJumpTime, repeatTime, noise, - modulation, + zmod, zcrush, zdelay, sustain, // sustain volume! @@ -104,14 +105,14 @@ function redableZZFX(params) { 'sustain', 'release', 'shape', - 'shapeCurve', + 'curve', 'slide', 'deltaSlide', 'pitchJump', 'pitchJumpTime', 'repeatTime', 'noise', - 'modulation', + 'zmod', 'zcrush', 'zdelay', 'sustainVolume', @@ -120,122 +121,3 @@ function redableZZFX(params) { ]; return Object.fromEntries(paramOrder.map((param, i) => [param, params[i]])); } - -// https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6 -// changes: replaced this.volume with 1 + using sampleRate from getAudioContext() -function buildSamples( - volume = 1, - randomness = 0.05, - frequency = 220, - attack = 0, - sustain = 0, - release = 0.1, - shape = 0, - shapeCurve = 1, - slide = 0, - deltaSlide = 0, - pitchJump = 0, - pitchJumpTime = 0, - repeatTime = 0, - noise = 0, - modulation = 0, - bitCrush = 0, - delay = 0, - sustainVolume = 1, - decay = 0, - tremolo = 0, -) { - // init parameters - let PI2 = Math.PI * 2, - sampleRate = getAudioContext().sampleRate, - sign = (v) => (v > 0 ? 1 : -1), - startSlide = (slide *= (500 * PI2) / sampleRate / sampleRate), - startFrequency = (frequency *= ((1 + randomness * 2 * Math.random() - randomness) * PI2) / sampleRate), - b = [], - t = 0, - tm = 0, - i = 0, - j = 1, - r = 0, - c = 0, - s = 0, - f, - length; - - // scale by sample rate - attack = attack * sampleRate + 9; // minimum attack to prevent pop - decay *= sampleRate; - sustain *= sampleRate; - release *= sampleRate; - delay *= sampleRate; - deltaSlide *= (500 * PI2) / sampleRate ** 3; - modulation *= PI2 / sampleRate; - pitchJump *= PI2 / sampleRate; - pitchJumpTime *= sampleRate; - repeatTime = (repeatTime * sampleRate) | 0; - - // generate waveform - for (length = (attack + decay + sustain + release + delay) | 0; i < length; b[i++] = s) { - if (!(++c % ((bitCrush * 100) | 0))) { - // bit crush - s = shape - ? shape > 1 - ? shape > 2 - ? shape > 3 // wave shape - ? Math.sin((t % PI2) ** 3) // 4 noise - : Math.max(Math.min(Math.tan(t), 1), -1) // 3 tan - : 1 - (((((2 * t) / PI2) % 2) + 2) % 2) // 2 saw - : 1 - 4 * Math.abs(Math.round(t / PI2) - t / PI2) // 1 triangle - : Math.sin(t); // 0 sin - - s = - (repeatTime - ? 1 - tremolo + tremolo * Math.sin((PI2 * i) / repeatTime) // tremolo - : 1) * - sign(s) * - Math.abs(s) ** shapeCurve * // curve 0=square, 2=pointy - volume * - 1 * // envelope - (i < attack - ? i / attack // attack - : i < attack + decay // decay - ? 1 - ((i - attack) / decay) * (1 - sustainVolume) // decay falloff - : i < attack + decay + sustain // sustain - ? sustainVolume // sustain volume - : i < length - delay // release - ? ((length - i - delay) / release) * // release falloff - sustainVolume // release volume - : 0); // post release - - s = delay - ? s / 2 + - (delay > i - ? 0 // delay - : ((i < length - delay ? 1 : (length - i) / delay) * // release delay - b[(i - delay) | 0]) / - 2) - : s; // sample delay - } - - f = - (frequency += slide += deltaSlide) * // frequency - Math.cos(modulation * tm++); // modulation - t += f - f * noise * (1 - (((Math.sin(i) + 1) * 1e9) % 2)); // noise - - if (j && ++j > pitchJumpTime) { - // pitch jump - frequency += pitchJump; // apply pitch jump - startFrequency += pitchJump; // also apply to start - j = 0; // stop pitch jump time - } - - if (repeatTime && !(++r % repeatTime)) { - // repeat - frequency = startFrequency; // reset frequency - slide = startSlide; // reset slide - j ||= 1; // reset pitch jump time - } - } - - return b; -} diff --git a/packages/superdough/zzfx_fork.mjs b/packages/superdough/zzfx_fork.mjs new file mode 100644 index 00000000..a2e496ff --- /dev/null +++ b/packages/superdough/zzfx_fork.mjs @@ -0,0 +1,118 @@ +// https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6 +// changes: replaced this.volume with 1 + using sampleRate from getAudioContext() +export function buildSamples( + volume = 1, + randomness = 0.05, + frequency = 220, + attack = 0, + sustain = 0, + release = 0.1, + shape = 0, + shapeCurve = 1, + slide = 0, + deltaSlide = 0, + pitchJump = 0, + pitchJumpTime = 0, + repeatTime = 0, + noise = 0, + modulation = 0, + bitCrush = 0, + delay = 0, + sustainVolume = 1, + decay = 0, + tremolo = 0, +) { + // init parameters + let PI2 = Math.PI * 2, + sampleRate = getAudioContext().sampleRate, + sign = (v) => (v > 0 ? 1 : -1), + startSlide = (slide *= (500 * PI2) / sampleRate / sampleRate), + startFrequency = (frequency *= ((1 + randomness * 2 * Math.random() - randomness) * PI2) / sampleRate), + b = [], + t = 0, + tm = 0, + i = 0, + j = 1, + r = 0, + c = 0, + s = 0, + f, + length; + + // scale by sample rate + attack = attack * sampleRate + 9; // minimum attack to prevent pop + decay *= sampleRate; + sustain *= sampleRate; + release *= sampleRate; + delay *= sampleRate; + deltaSlide *= (500 * PI2) / sampleRate ** 3; + modulation *= PI2 / sampleRate; + pitchJump *= PI2 / sampleRate; + pitchJumpTime *= sampleRate; + repeatTime = (repeatTime * sampleRate) | 0; + + // generate waveform + for (length = (attack + decay + sustain + release + delay) | 0; i < length; b[i++] = s) { + if (!(++c % ((bitCrush * 100) | 0))) { + // bit crush + s = shape + ? shape > 1 + ? shape > 2 + ? shape > 3 // wave shape + ? Math.sin((t % PI2) ** 3) // 4 noise + : Math.max(Math.min(Math.tan(t), 1), -1) // 3 tan + : 1 - (((((2 * t) / PI2) % 2) + 2) % 2) // 2 saw + : 1 - 4 * Math.abs(Math.round(t / PI2) - t / PI2) // 1 triangle + : Math.sin(t); // 0 sin + + s = + (repeatTime + ? 1 - tremolo + tremolo * Math.sin((PI2 * i) / repeatTime) // tremolo + : 1) * + sign(s) * + Math.abs(s) ** shapeCurve * // curve 0=square, 2=pointy + volume * + 1 * // envelope + (i < attack + ? i / attack // attack + : i < attack + decay // decay + ? 1 - ((i - attack) / decay) * (1 - sustainVolume) // decay falloff + : i < attack + decay + sustain // sustain + ? sustainVolume // sustain volume + : i < length - delay // release + ? ((length - i - delay) / release) * // release falloff + sustainVolume // release volume + : 0); // post release + + s = delay + ? s / 2 + + (delay > i + ? 0 // delay + : ((i < length - delay ? 1 : (length - i) / delay) * // release delay + b[(i - delay) | 0]) / + 2) + : s; // sample delay + } + + f = + (frequency += slide += deltaSlide) * // frequency + Math.cos(modulation * tm++); // modulation + t += f - f * noise * (1 - (((Math.sin(i) + 1) * 1e9) % 2)); // noise + + if (j && ++j > pitchJumpTime) { + // pitch jump + frequency += pitchJump; // apply pitch jump + startFrequency += pitchJump; // also apply to start + j = 0; // stop pitch jump time + } + + if (repeatTime && !(++r % repeatTime)) { + // repeat + frequency = startFrequency; // reset frequency + slide = startSlide; // reset slide + j ||= 1; // reset pitch jump time + } + } + + return b; +} From 4a122523d922ad44075a468169e733dda43ba98b Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 07:55:09 +0200 Subject: [PATCH 11/17] fix: import --- packages/superdough/zzfx_fork.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/superdough/zzfx_fork.mjs b/packages/superdough/zzfx_fork.mjs index a2e496ff..8c6bdbb5 100644 --- a/packages/superdough/zzfx_fork.mjs +++ b/packages/superdough/zzfx_fork.mjs @@ -1,3 +1,5 @@ +import { getAudioContext } from './superdough.mjs'; + // https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6 // changes: replaced this.volume with 1 + using sampleRate from getAudioContext() export function buildSamples( From 24ec47e4ffd84c16a6e453b7cf4448a6e9fb1172 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 07:58:31 +0200 Subject: [PATCH 12/17] fix: comments --- packages/core/controls.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 47d77253..b5dcc094 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -813,8 +813,8 @@ const generic_params = [ ['repeatTime'], ['noise'], ['zmod'], - ['zcrush'], // like crush.. - ['zdelay'], // duplicate + ['zcrush'], // like crush but scaled differently + ['zdelay'], ['tremolo'], ]; // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 From 6ec5377381b9772158122e4f4228ad3089f1e3e3 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 09:46:30 +0200 Subject: [PATCH 13/17] add lfo alias for repeatTime --- packages/core/controls.mjs | 2 +- packages/superdough/zzfx.mjs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index b5dcc094..4982ec53 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -810,7 +810,7 @@ const generic_params = [ ['deltaSlide'], ['pitchJump'], ['pitchJumpTime'], - ['repeatTime'], + ['lfo', 'repeatTime'], ['noise'], ['zmod'], ['zcrush'], // like crush but scaled differently diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs index c57901fb..be2c479a 100644 --- a/packages/superdough/zzfx.mjs +++ b/packages/superdough/zzfx.mjs @@ -19,7 +19,7 @@ export const getZZFX = (value, t) => { deltaSlide = 0, pitchJump = 0, pitchJumpTime = 0, - repeatTime = 0, + lfo = 0, noise = 0, zmod = 0, zcrush = 0, @@ -52,7 +52,7 @@ export const getZZFX = (value, t) => { deltaSlide, pitchJump, pitchJumpTime, - repeatTime, + lfo, noise, zmod, zcrush, @@ -110,7 +110,7 @@ function redableZZFX(params) { 'deltaSlide', 'pitchJump', 'pitchJumpTime', - 'repeatTime', + 'lfo', 'noise', 'zmod', 'zcrush', From 388e7b3c2fa5fece0aa93616484e67f19254cd4c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 09:46:36 +0200 Subject: [PATCH 14/17] add doc --- website/src/pages/learn/synths.mdx | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index 6ebf2613..96404a43 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -38,4 +38,41 @@ Now we not only pattern the notes, but the sound as well! +## ZZFX + +The "Zuper Zmall Zound Zynth" [ZZFX](https://github.com/KilledByAPixel/ZzFX) is also integrated in strudel. +Developed by [Frank Force](https://frankforce.com/), it is a synth and FX engine originally intended to be used for size coding games. + +It has 20 parameters in total, here is a snippet that uses all: + +") // also supports freq + .s("") + .zrand(0) // randomization + // zzfx envelope + .attack(0.001) + .decay(0.1) + .sustain(.8) + .release(.1) + // special zzfx params + .curve(1) // waveshape 1-3 + .slide(0) // +/- pitch slide + .deltaSlide(0) // +/- pitch slide (?) + .noise(0) // make it dirty + .zmod(0) // fm speed + .zcrush(0) // bit crush 0 - 1 + .zdelay(0) // simple delay + .pitchJump(0) // +/- pitch change after pitchJumpTime + .pitchJumpTime(0) // >0 time after pitchJump is applied + .lfo(0) // >0 resets slide + pitchJump + sets tremolo speed + .tremolo(0) // 0-1 lfo volume modulation amount + //.duration(.2) // overwrite strudel event duration + //.gain(1) // change volume + .scope() // vizualise waveform (not zzfx related) +`} +/> + +Note that you can also combine zzfx with all the other audio fx (next chapter). + Next up: [Audio Effects](/learn/effects)... From 4c744da3e2ca73fd06eb78e3d67284fc9690387e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 10:52:36 +0200 Subject: [PATCH 15/17] add zzfx sound + zzfx control to override params --- packages/core/controls.mjs | 1 + packages/superdough/zzfx.mjs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 4982ec53..92eda843 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -816,6 +816,7 @@ const generic_params = [ ['zcrush'], // like crush but scaled differently ['zdelay'], ['tremolo'], + ['zzfx'], ]; // TODO: slice / splice https://www.youtube.com/watch?v=hKhPdO0RKDQ&list=PL2lW1zNIIwj3bDkh-Y3LUGDuRcoUigoDs&index=13 diff --git a/packages/superdough/zzfx.mjs b/packages/superdough/zzfx.mjs index be2c479a..da505d74 100644 --- a/packages/superdough/zzfx.mjs +++ b/packages/superdough/zzfx.mjs @@ -26,6 +26,7 @@ export const getZZFX = (value, t) => { zdelay = 0, tremolo = 0, duration = 0.2, + zzfx, } = value; const sustainTime = Math.max(duration - attack - decay, 0); if (typeof note === 'string') { @@ -39,7 +40,7 @@ export const getZZFX = (value, t) => { const shape = ['sine', 'triangle', 'sawtooth', 'tan', 'noise'].indexOf(s) || 0; curve = s === 'square' ? 0 : curve; - const params = [ + const params = zzfx || [ 0.25, // volume zrand, freq, @@ -76,7 +77,7 @@ export const getZZFX = (value, t) => { }; export function registerZZFXSounds() { - ['z_sine', 'z_sawtooth', 'z_triangle', 'z_square', 'z_tan', 'z_noise'].forEach((wave) => { + ['zzfx', 'z_sine', 'z_sawtooth', 'z_triangle', 'z_square', 'z_tan', 'z_noise'].forEach((wave) => { registerSound( wave, (t, value, onended) => { From 8dd08c360f27b9dddb8ff565ad4c1fb2ec6c83c5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 12:56:18 +0200 Subject: [PATCH 16/17] improve synth docs --- packages/core/controls.mjs | 6 ++++ website/src/pages/learn/synths.mdx | 48 ++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 3ca691d9..71f58427 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -121,6 +121,7 @@ const generic_params = [ * note("c e g b") * .fm(4) * .fmh("<1 2 1.5 1.61>") + * .scope() * */ [['fmh', 'fmi'], 'fmh'], @@ -134,6 +135,7 @@ const generic_params = [ * @example * note("c e g b") * .fm("<0 1 2 8 32>") + * .scope() * */ [['fmi', 'fmh'], 'fm'], @@ -149,6 +151,7 @@ const generic_params = [ * .fmdecay(.2) * .fmsustain(0) * .fmenv("") + * .scope() * */ ['fmenv'], @@ -161,6 +164,7 @@ const generic_params = [ * note("c e g b") * .fm(4) * .fmattack("<0 .05 .1 .2>") + * .scope() * */ ['fmattack'], @@ -174,6 +178,7 @@ const generic_params = [ * .fm(4) * .fmdecay("<.01 .05 .1 .2>") * .fmsustain(.4) + * .scope() * */ ['fmdecay'], @@ -187,6 +192,7 @@ const generic_params = [ * .fm(4) * .fmdecay(.1) * .fmsustain("<1 .75 .5 0>") + * .scope() * */ ['fmsustain'], diff --git a/website/src/pages/learn/synths.mdx b/website/src/pages/learn/synths.mdx index a715f8ba..b24726cf 100644 --- a/website/src/pages/learn/synths.mdx +++ b/website/src/pages/learn/synths.mdx @@ -8,28 +8,52 @@ import { JsDoc } from '../../docs/JsDoc'; # Synths -For now, [samples](/learn/samples) are the main way to play with Strudel. -In the future, more powerful synthesis capabilities will be added. -If in the meantime you want to dive deeper into audio synthesis with Tidal, you will need to [install SuperCollider and SuperDirt](https://tidalcycles.org/docs/). +In addition to the sampling engine, strudel comes with a synthesizer to create sounds on the fly. -## Playing synths with `s` +## Basic Waveforms -We can change the sound, using the `s` function: +The basic waveforms are `sine`, `sawtooth`, `square` and `triangle`, which can be selected via `sound` (or `s`): ->").s('sawtooth')`} /> +>") +.sound("") +.scope()`} +/> -Here, we are wrapping our notes inside `note` and set the sound using `s`, connected by a dot. +If you don't set a `sound` but a `note` the default value for `sound` is `triangle`! -Those functions are only 2 of many ways to alter the properties, or _params_ of a sound. -The power of patterns allows us to sequence any _param_ independently: +### Additive Synthesis ->").s("")`} /> +To tame the harsh sound of the basic waveforms, we can set the `n` control to limit the overtones of the waveform: -Now we not only pattern the notes, but the sound as well! -`sawtooth` `square` and `triangle` are the basic waveforms available in `s`. +>") +.sound("sawtooth") +.n("<32 16 8 4>") +.scope()`} +/> + +When the `n` control is used on a basic waveform, it defines the number of harmonic partials the sound is getting. +You can also set `n` directly in mini notation with `sound`: + +>") +.sound("sawtooth:<32 16 8 4>") +.scope()`} +/> + +Note for tidal users: `n` in tidal is synonymous to `note` for synths only. +In strudel, this is not the case, where `n` will always change timbre, be it though different samples or different waveforms. ## FM Synthesis +FM Synthesis is a technique that changes the frequency of a basic waveform rapidly to alter the timbre. + +You can use fm with any of the above waveforms, although the below examples all use the default triangle wave. + ### fm From c9d98bf4d7b788e17860dc7b7f5b3cb4aa68f35f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 31 Aug 2023 12:58:32 +0200 Subject: [PATCH 17/17] fix: tests --- packages/core/draw.mjs | 3 + test/__snapshots__/examples.test.mjs.snap | 192 +++++++++++----------- 2 files changed, 99 insertions(+), 96 deletions(-) diff --git a/packages/core/draw.mjs b/packages/core/draw.mjs index d1cdd7be..c57baa63 100644 --- a/packages/core/draw.mjs +++ b/packages/core/draw.mjs @@ -29,6 +29,9 @@ export const getDrawContext = (id = 'test-canvas') => { }; Pattern.prototype.draw = function (callback, { from, to, onQuery } = {}) { + if (typeof window === 'undefined') { + return this; + } if (window.strudelAnimation) { cancelAnimationFrame(window.strudelAnimation); } diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 787bc461..60b39c53 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1806,127 +1806,127 @@ exports[`runs examples > example "floor" example index 0 1`] = ` exports[`runs examples > example "fm" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | note:c fmi:0 ]", - "[ 1/4 → 1/2 | note:e fmi:0 ]", - "[ 1/2 → 3/4 | note:g fmi:0 ]", - "[ 3/4 → 1/1 | note:b fmi:0 ]", - "[ 1/1 → 5/4 | note:c fmi:1 ]", - "[ 5/4 → 3/2 | note:e fmi:1 ]", - "[ 3/2 → 7/4 | note:g fmi:1 ]", - "[ 7/4 → 2/1 | note:b fmi:1 ]", - "[ 2/1 → 9/4 | note:c fmi:2 ]", - "[ 9/4 → 5/2 | note:e fmi:2 ]", - "[ 5/2 → 11/4 | note:g fmi:2 ]", - "[ 11/4 → 3/1 | note:b fmi:2 ]", - "[ 3/1 → 13/4 | note:c fmi:8 ]", - "[ 13/4 → 7/2 | note:e fmi:8 ]", - "[ 7/2 → 15/4 | note:g fmi:8 ]", - "[ 15/4 → 4/1 | note:b fmi:8 ]", + "[ 0/1 → 1/4 | note:c fmi:0 analyze:1 ]", + "[ 1/4 → 1/2 | note:e fmi:0 analyze:1 ]", + "[ 1/2 → 3/4 | note:g fmi:0 analyze:1 ]", + "[ 3/4 → 1/1 | note:b fmi:0 analyze:1 ]", + "[ 1/1 → 5/4 | note:c fmi:1 analyze:1 ]", + "[ 5/4 → 3/2 | note:e fmi:1 analyze:1 ]", + "[ 3/2 → 7/4 | note:g fmi:1 analyze:1 ]", + "[ 7/4 → 2/1 | note:b fmi:1 analyze:1 ]", + "[ 2/1 → 9/4 | note:c fmi:2 analyze:1 ]", + "[ 9/4 → 5/2 | note:e fmi:2 analyze:1 ]", + "[ 5/2 → 11/4 | note:g fmi:2 analyze:1 ]", + "[ 11/4 → 3/1 | note:b fmi:2 analyze:1 ]", + "[ 3/1 → 13/4 | note:c fmi:8 analyze:1 ]", + "[ 13/4 → 7/2 | note:e fmi:8 analyze:1 ]", + "[ 7/2 → 15/4 | note:g fmi:8 analyze:1 ]", + "[ 15/4 → 4/1 | note:b fmi:8 analyze:1 ]", ] `; exports[`runs examples > example "fmattack" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | note:c fmi:4 fmattack:0 ]", - "[ 1/4 → 1/2 | note:e fmi:4 fmattack:0 ]", - "[ 1/2 → 3/4 | note:g fmi:4 fmattack:0 ]", - "[ 3/4 → 1/1 | note:b fmi:4 fmattack:0 ]", - "[ 1/1 → 5/4 | note:c fmi:4 fmattack:0.05 ]", - "[ 5/4 → 3/2 | note:e fmi:4 fmattack:0.05 ]", - "[ 3/2 → 7/4 | note:g fmi:4 fmattack:0.05 ]", - "[ 7/4 → 2/1 | note:b fmi:4 fmattack:0.05 ]", - "[ 2/1 → 9/4 | note:c fmi:4 fmattack:0.1 ]", - "[ 9/4 → 5/2 | note:e fmi:4 fmattack:0.1 ]", - "[ 5/2 → 11/4 | note:g fmi:4 fmattack:0.1 ]", - "[ 11/4 → 3/1 | note:b fmi:4 fmattack:0.1 ]", - "[ 3/1 → 13/4 | note:c fmi:4 fmattack:0.2 ]", - "[ 13/4 → 7/2 | note:e fmi:4 fmattack:0.2 ]", - "[ 7/2 → 15/4 | note:g fmi:4 fmattack:0.2 ]", - "[ 15/4 → 4/1 | note:b fmi:4 fmattack:0.2 ]", + "[ 0/1 → 1/4 | note:c fmi:4 fmattack:0 analyze:1 ]", + "[ 1/4 → 1/2 | note:e fmi:4 fmattack:0 analyze:1 ]", + "[ 1/2 → 3/4 | note:g fmi:4 fmattack:0 analyze:1 ]", + "[ 3/4 → 1/1 | note:b fmi:4 fmattack:0 analyze:1 ]", + "[ 1/1 → 5/4 | note:c fmi:4 fmattack:0.05 analyze:1 ]", + "[ 5/4 → 3/2 | note:e fmi:4 fmattack:0.05 analyze:1 ]", + "[ 3/2 → 7/4 | note:g fmi:4 fmattack:0.05 analyze:1 ]", + "[ 7/4 → 2/1 | note:b fmi:4 fmattack:0.05 analyze:1 ]", + "[ 2/1 → 9/4 | note:c fmi:4 fmattack:0.1 analyze:1 ]", + "[ 9/4 → 5/2 | note:e fmi:4 fmattack:0.1 analyze:1 ]", + "[ 5/2 → 11/4 | note:g fmi:4 fmattack:0.1 analyze:1 ]", + "[ 11/4 → 3/1 | note:b fmi:4 fmattack:0.1 analyze:1 ]", + "[ 3/1 → 13/4 | note:c fmi:4 fmattack:0.2 analyze:1 ]", + "[ 13/4 → 7/2 | note:e fmi:4 fmattack:0.2 analyze:1 ]", + "[ 7/2 → 15/4 | note:g fmi:4 fmattack:0.2 analyze:1 ]", + "[ 15/4 → 4/1 | note:b fmi:4 fmattack:0.2 analyze:1 ]", ] `; exports[`runs examples > example "fmdecay" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.01 fmsustain:0.4 ]", - "[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.01 fmsustain:0.4 ]", - "[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.01 fmsustain:0.4 ]", - "[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.01 fmsustain:0.4 ]", - "[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.05 fmsustain:0.4 ]", - "[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.05 fmsustain:0.4 ]", - "[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.05 fmsustain:0.4 ]", - "[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.05 fmsustain:0.4 ]", - "[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.4 ]", - "[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.4 ]", - "[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.4 ]", - "[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.4 ]", - "[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0.4 ]", - "[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0.4 ]", - "[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0.4 ]", - "[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0.4 ]", + "[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]", + "[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]", + "[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]", + "[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]", + "[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]", + "[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]", + "[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]", + "[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]", + "[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]", + "[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]", + "[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]", + "[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]", + "[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]", + "[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]", + "[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]", + "[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]", ] `; exports[`runs examples > example "fmenv" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]", - "[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]", - "[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]", - "[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]", - "[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]", - "[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]", - "[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]", - "[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]", - "[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]", - "[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]", - "[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]", - "[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]", - "[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]", - "[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]", - "[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]", - "[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]", + "[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]", + "[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]", + "[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]", + "[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]", + "[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]", + "[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]", + "[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]", + "[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]", + "[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]", + "[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]", + "[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]", + "[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]", + "[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]", + "[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]", + "[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]", + "[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]", ] `; exports[`runs examples > example "fmh" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | note:c fmi:4 fmh:1 ]", - "[ 1/4 → 1/2 | note:e fmi:4 fmh:1 ]", - "[ 1/2 → 3/4 | note:g fmi:4 fmh:1 ]", - "[ 3/4 → 1/1 | note:b fmi:4 fmh:1 ]", - "[ 1/1 → 5/4 | note:c fmi:4 fmh:2 ]", - "[ 5/4 → 3/2 | note:e fmi:4 fmh:2 ]", - "[ 3/2 → 7/4 | note:g fmi:4 fmh:2 ]", - "[ 7/4 → 2/1 | note:b fmi:4 fmh:2 ]", - "[ 2/1 → 9/4 | note:c fmi:4 fmh:1.5 ]", - "[ 9/4 → 5/2 | note:e fmi:4 fmh:1.5 ]", - "[ 5/2 → 11/4 | note:g fmi:4 fmh:1.5 ]", - "[ 11/4 → 3/1 | note:b fmi:4 fmh:1.5 ]", - "[ 3/1 → 13/4 | note:c fmi:4 fmh:1.61 ]", - "[ 13/4 → 7/2 | note:e fmi:4 fmh:1.61 ]", - "[ 7/2 → 15/4 | note:g fmi:4 fmh:1.61 ]", - "[ 15/4 → 4/1 | note:b fmi:4 fmh:1.61 ]", + "[ 0/1 → 1/4 | note:c fmi:4 fmh:1 analyze:1 ]", + "[ 1/4 → 1/2 | note:e fmi:4 fmh:1 analyze:1 ]", + "[ 1/2 → 3/4 | note:g fmi:4 fmh:1 analyze:1 ]", + "[ 3/4 → 1/1 | note:b fmi:4 fmh:1 analyze:1 ]", + "[ 1/1 → 5/4 | note:c fmi:4 fmh:2 analyze:1 ]", + "[ 5/4 → 3/2 | note:e fmi:4 fmh:2 analyze:1 ]", + "[ 3/2 → 7/4 | note:g fmi:4 fmh:2 analyze:1 ]", + "[ 7/4 → 2/1 | note:b fmi:4 fmh:2 analyze:1 ]", + "[ 2/1 → 9/4 | note:c fmi:4 fmh:1.5 analyze:1 ]", + "[ 9/4 → 5/2 | note:e fmi:4 fmh:1.5 analyze:1 ]", + "[ 5/2 → 11/4 | note:g fmi:4 fmh:1.5 analyze:1 ]", + "[ 11/4 → 3/1 | note:b fmi:4 fmh:1.5 analyze:1 ]", + "[ 3/1 → 13/4 | note:c fmi:4 fmh:1.61 analyze:1 ]", + "[ 13/4 → 7/2 | note:e fmi:4 fmh:1.61 analyze:1 ]", + "[ 7/2 → 15/4 | note:g fmi:4 fmh:1.61 analyze:1 ]", + "[ 15/4 → 4/1 | note:b fmi:4 fmh:1.61 analyze:1 ]", ] `; exports[`runs examples > example "fmsustain" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.1 fmsustain:1 ]", - "[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.1 fmsustain:1 ]", - "[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.1 fmsustain:1 ]", - "[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.1 fmsustain:1 ]", - "[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.75 ]", - "[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.75 ]", - "[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.75 ]", - "[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.75 ]", - "[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.5 ]", - "[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.5 ]", - "[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.5 ]", - "[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.5 ]", - "[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0 ]", - "[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0 ]", - "[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0 ]", - "[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0 ]", + "[ 0/1 → 1/4 | note:c fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]", + "[ 1/4 → 1/2 | note:e fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]", + "[ 1/2 → 3/4 | note:g fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]", + "[ 3/4 → 1/1 | note:b fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]", + "[ 1/1 → 5/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]", + "[ 5/4 → 3/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]", + "[ 3/2 → 7/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]", + "[ 7/4 → 2/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]", + "[ 2/1 → 9/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]", + "[ 9/4 → 5/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]", + "[ 5/2 → 11/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]", + "[ 11/4 → 3/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]", + "[ 3/1 → 13/4 | note:c fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]", + "[ 13/4 → 7/2 | note:e fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]", + "[ 7/2 → 15/4 | note:g fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]", + "[ 15/4 → 4/1 | note:b fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]", ] `;