CSound based engine
This commit is contained in:
@ -22,6 +22,7 @@
|
||||
"vite": "npm:rolldown-vite@7.1.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@csound/browser": "7.0.0-beta11",
|
||||
"zzfx": "^1.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
383
pnpm-lock.yaml
generated
383
pnpm-lock.yaml
generated
@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@csound/browser':
|
||||
specifier: 7.0.0-beta11
|
||||
version: 7.0.0-beta11
|
||||
zzfx:
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2
|
||||
@ -36,6 +39,13 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@babel/runtime@7.28.4':
|
||||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@csound/browser@7.0.0-beta11':
|
||||
resolution: {integrity: sha512-BGFTMXUdOJA1Xz1ETzbE/y8B/X6dpnrKThiqxDqj45K+ctOWtMqefgH6MojzJjWFwRs8UqhrJmVUq78SbMwGlw==}
|
||||
|
||||
'@emnapi/core@1.5.0':
|
||||
resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==}
|
||||
|
||||
@ -194,6 +204,10 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ansis@4.2.0:
|
||||
resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
|
||||
engines: {node: '>=14'}
|
||||
@ -202,18 +216,59 @@ packages:
|
||||
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
automation-events@7.1.13:
|
||||
resolution: {integrity: sha512-1Hay5TQPzxsskSqPTH3YXyzE9Iirz82zZDse2vr3+kOR7Sc7om17qIEPsESchlNX0EgKxANwR40i2g/O3GM1Tw==}
|
||||
engines: {node: '>=18.2.0'}
|
||||
|
||||
axobject-query@4.1.0:
|
||||
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
clone-buffer@1.0.0:
|
||||
resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
clone-stats@1.0.0:
|
||||
resolution: {integrity: sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==}
|
||||
|
||||
clone@2.1.2:
|
||||
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
cloneable-readable@1.1.3:
|
||||
resolution: {integrity: sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
@ -237,6 +292,9 @@ packages:
|
||||
esrap@2.1.0:
|
||||
resolution: {integrity: sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==}
|
||||
|
||||
eventemitter3@4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@ -246,14 +304,68 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
|
||||
google-closure-compiler-java@20221102.0.1:
|
||||
resolution: {integrity: sha512-rMKLEma3uSe/6MGHtivDezTv4u5iaDEyxoy9No+1WruPSZ5h1gBZLONcfCA8JaoGojFPdHZI1qbwT0EveEWnAg==}
|
||||
|
||||
google-closure-compiler-linux@20221102.0.1:
|
||||
resolution: {integrity: sha512-rj1E1whT4j/giidQ44v4RoO8GcvU81VU9YB5RlRM0hWDvCGWjQasDABGnF/YLWLl5PXAAfJpa/hy8ckv5/r97g==}
|
||||
cpu: [x32, x64]
|
||||
os: [linux]
|
||||
|
||||
google-closure-compiler-osx@20221102.0.1:
|
||||
resolution: {integrity: sha512-Cv993yr9a2DLFgYnsv4m6dNUk5jousd6W6la12x2fDbhxTLewYrw7CrCaVEVw1SU3XErVmdHOZQjFsVMhcZjCw==}
|
||||
cpu: [x32, x64, arm64]
|
||||
os: [darwin]
|
||||
|
||||
google-closure-compiler-windows@20221102.0.1:
|
||||
resolution: {integrity: sha512-jRwHGekG/oDihHdKAEiYN5z0cBF+brL0bYtuEOXx4fAmq5tHe4OxKtSEEprCnVZZL0aG/boGprACPvsDRsXT7Q==}
|
||||
cpu: [x32, x64]
|
||||
os: [win32]
|
||||
|
||||
google-closure-compiler@20221102.0.1:
|
||||
resolution: {integrity: sha512-edAlsyJEsy2I983xWBlBfdSme16uyY007HM2OwPOoWPEFgmR100ggUabJbIegXZgbSLH51kyeJMQKuWhiHgzcA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
google-closure-library@20221102.0.0:
|
||||
resolution: {integrity: sha512-M5+LWPS99tMB9dOGpZjLT9CdIYpnwBZiwB+dCmZFOOvwJiOWytntzJ/a/hoNF6zxD15l3GWwRJiEkL636D6DRQ==}
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
is-reference@3.0.3:
|
||||
resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
|
||||
|
||||
isarray@1.0.0:
|
||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||
|
||||
jazz-midi@1.7.9:
|
||||
resolution: {integrity: sha512-c8c4BBgwxdsIr1iVm53nadCrtH7BUlnX3V95ciK/gbvXN/ndE5+POskBalXgqlc/r9p2XUbdLTrgrC6fou5p9w==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
jzz@1.9.6:
|
||||
resolution: {integrity: sha512-J7ENLhXwfm2BNDKRUrL8eKtPhUS/CtMBpiafxQHDBcOWSocLhearDKEdh+ylnZFcr5OXWTed0gj6l/txeQA9vg==}
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@ -330,6 +442,12 @@ packages:
|
||||
magic-string@0.30.19:
|
||||
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
||||
minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
mri@1.2.0:
|
||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
engines: {node: '>=4'}
|
||||
@ -342,6 +460,16 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
pako@2.1.0:
|
||||
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
|
||||
|
||||
path-is-absolute@1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@ -353,10 +481,31 @@ packages:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
ramda@0.28.0:
|
||||
resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
remove-trailing-separator@1.1.0:
|
||||
resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==}
|
||||
|
||||
replace-ext@1.0.1:
|
||||
resolution: {integrity: sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
|
||||
rolldown-vite@7.1.14:
|
||||
resolution: {integrity: sha512-eSiiRJmovt8qDJkGyZuLnbxAOAdie6NCmmd0NkTC0RJI9duiSBTfr8X2mBYJOUFzxQa2USaHmL99J9uMxkjCyw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@ -406,10 +555,27 @@ packages:
|
||||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
safe-buffer@5.1.2:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map@0.5.7:
|
||||
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
standardized-audio-context@25.3.77:
|
||||
resolution: {integrity: sha512-Ki9zNz6pKcC5Pi+QPjPyVsD9GwJIJWgryji0XL9cAJXMGyn+dPOf6Qik1AHei0+UNVcc4BOCa0hWLBzlwqsW/A==}
|
||||
|
||||
string_decoder@1.1.1:
|
||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
svelte-check@4.3.3:
|
||||
resolution: {integrity: sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==}
|
||||
engines: {node: '>= 18.0.0'}
|
||||
@ -422,6 +588,9 @@ packages:
|
||||
resolution: {integrity: sha512-8MxWVm2+3YwrFbPaxOlT1bbMi6OTenrAgks6soZfiaS8Fptk4EVyRIFhJc3RpO264EeSNwgjWAdki0ufg4zkGw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
text-encoding-shim@1.0.5:
|
||||
resolution: {integrity: sha512-H7yYW+jRn4yhu60ygZ2f/eMhXPITRt4QSUTKzLm+eCaDsdX8avmgWpmtmHAzesjBVUTAypz9odu5RKUjX5HNYA==}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@ -437,6 +606,19 @@ packages:
|
||||
undici-types@7.14.0:
|
||||
resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==}
|
||||
|
||||
unmute-ios-audio@3.3.0:
|
||||
resolution: {integrity: sha512-MmoCOrsS2gn3wLT2tT+hF56Q4V4kksIKn2LHrwAtX6umzQwQHDWSh1slMzH+0WuxTZ62s3w8/wsfIII1FQ7ACg==}
|
||||
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
vinyl-sourcemaps-apply@0.2.1:
|
||||
resolution: {integrity: sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==}
|
||||
|
||||
vinyl@2.2.1:
|
||||
resolution: {integrity: sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
vitefu@1.1.1:
|
||||
resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==}
|
||||
peerDependencies:
|
||||
@ -445,6 +627,12 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
web-midi-api@2.4.0:
|
||||
resolution: {integrity: sha512-tTfLdxa5LpOP1NgWByV458iYKgSLhlsIwqCpfbcJuyjProNtuf5UnX97K4JNyuQCqkR+6thQAIsk2BOMSrKaCA==}
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
zimmerframe@1.1.4:
|
||||
resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==}
|
||||
|
||||
@ -453,6 +641,21 @@ packages:
|
||||
|
||||
snapshots:
|
||||
|
||||
'@babel/runtime@7.28.4': {}
|
||||
|
||||
'@csound/browser@7.0.0-beta11':
|
||||
dependencies:
|
||||
eventemitter3: 4.0.7
|
||||
google-closure-compiler: 20221102.0.1
|
||||
google-closure-library: 20221102.0.0
|
||||
pako: 2.1.0
|
||||
ramda: 0.28.0
|
||||
rimraf: 3.0.2
|
||||
standardized-audio-context: 25.3.77
|
||||
text-encoding-shim: 1.0.5
|
||||
unmute-ios-audio: 3.3.0
|
||||
web-midi-api: 2.4.0
|
||||
|
||||
'@emnapi/core@1.5.0':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.1.0
|
||||
@ -585,18 +788,61 @@ snapshots:
|
||||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
|
||||
ansis@4.2.0: {}
|
||||
|
||||
aria-query@5.3.2: {}
|
||||
|
||||
automation-events@7.1.13:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
tslib: 2.8.1
|
||||
|
||||
axobject-query@4.1.0: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
clone-buffer@1.0.0: {}
|
||||
|
||||
clone-stats@1.0.0: {}
|
||||
|
||||
clone@2.1.2: {}
|
||||
|
||||
cloneable-readable@1.1.3:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
process-nextick-args: 2.0.1
|
||||
readable-stream: 2.3.8
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@ -611,17 +857,72 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
eventemitter3@4.0.7: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
glob@7.2.3:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.1.2
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
|
||||
google-closure-compiler-java@20221102.0.1: {}
|
||||
|
||||
google-closure-compiler-linux@20221102.0.1:
|
||||
optional: true
|
||||
|
||||
google-closure-compiler-osx@20221102.0.1:
|
||||
optional: true
|
||||
|
||||
google-closure-compiler-windows@20221102.0.1:
|
||||
optional: true
|
||||
|
||||
google-closure-compiler@20221102.0.1:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
google-closure-compiler-java: 20221102.0.1
|
||||
minimist: 1.2.8
|
||||
vinyl: 2.2.1
|
||||
vinyl-sourcemaps-apply: 0.2.1
|
||||
optionalDependencies:
|
||||
google-closure-compiler-linux: 20221102.0.1
|
||||
google-closure-compiler-osx: 20221102.0.1
|
||||
google-closure-compiler-windows: 20221102.0.1
|
||||
|
||||
google-closure-library@20221102.0.0: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
inflight@1.0.6:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
is-reference@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
||||
isarray@1.0.0: {}
|
||||
|
||||
jazz-midi@1.7.9: {}
|
||||
|
||||
jzz@1.9.6:
|
||||
dependencies:
|
||||
jazz-midi: 1.7.9
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
optional: true
|
||||
|
||||
@ -677,12 +978,26 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
mri@1.2.0: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
pako@2.1.0: {}
|
||||
|
||||
path-is-absolute@1.0.1: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
@ -693,8 +1008,30 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
|
||||
ramda@0.28.0: {}
|
||||
|
||||
readable-stream@2.3.8:
|
||||
dependencies:
|
||||
core-util-is: 1.0.3
|
||||
inherits: 2.0.4
|
||||
isarray: 1.0.0
|
||||
process-nextick-args: 2.0.1
|
||||
safe-buffer: 5.1.2
|
||||
string_decoder: 1.1.1
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
remove-trailing-separator@1.1.0: {}
|
||||
|
||||
replace-ext@1.0.1: {}
|
||||
|
||||
rimraf@3.0.2:
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
|
||||
rolldown-vite@7.1.14(@types/node@24.7.1):
|
||||
dependencies:
|
||||
'@oxc-project/runtime': 0.92.0
|
||||
@ -733,8 +1070,26 @@ snapshots:
|
||||
dependencies:
|
||||
mri: 1.2.0
|
||||
|
||||
safe-buffer@5.1.2: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
source-map@0.5.7: {}
|
||||
|
||||
standardized-audio-context@25.3.77:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
automation-events: 7.1.13
|
||||
tslib: 2.8.1
|
||||
|
||||
string_decoder@1.1.1:
|
||||
dependencies:
|
||||
safe-buffer: 5.1.2
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
svelte-check@4.3.3(picomatch@4.0.3)(svelte@5.39.11)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
@ -764,22 +1119,46 @@ snapshots:
|
||||
magic-string: 0.30.19
|
||||
zimmerframe: 1.1.4
|
||||
|
||||
text-encoding-shim@1.0.5: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tslib@2.8.1:
|
||||
optional: true
|
||||
tslib@2.8.1: {}
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@7.14.0: {}
|
||||
|
||||
unmute-ios-audio@3.3.0: {}
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vinyl-sourcemaps-apply@0.2.1:
|
||||
dependencies:
|
||||
source-map: 0.5.7
|
||||
|
||||
vinyl@2.2.1:
|
||||
dependencies:
|
||||
clone: 2.1.2
|
||||
clone-buffer: 1.0.0
|
||||
clone-stats: 1.0.0
|
||||
cloneable-readable: 1.1.3
|
||||
remove-trailing-separator: 1.1.0
|
||||
replace-ext: 1.0.1
|
||||
|
||||
vitefu@1.1.1(rolldown-vite@7.1.14(@types/node@24.7.1)):
|
||||
optionalDependencies:
|
||||
vite: rolldown-vite@7.1.14(@types/node@24.7.1)
|
||||
|
||||
web-midi-api@2.4.0:
|
||||
dependencies:
|
||||
jzz: 1.9.6
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
zimmerframe@1.1.4: {}
|
||||
|
||||
zzfx@1.3.2: {}
|
||||
|
||||
@ -185,11 +185,11 @@
|
||||
pitchLockEnabled = !pitchLockEnabled;
|
||||
}
|
||||
|
||||
function regenerateBuffer() {
|
||||
async function regenerateBuffer() {
|
||||
if (!currentParams) return;
|
||||
|
||||
const sampleRate = audioService.getSampleRate();
|
||||
const data = engine.generate(currentParams, sampleRate, duration);
|
||||
const data = await engine.generate(currentParams, sampleRate, duration, pitchLock);
|
||||
currentBuffer = audioService.createAudioBuffer(data);
|
||||
audioService.play(currentBuffer);
|
||||
}
|
||||
|
||||
253
src/lib/audio/engines/CsoundEngine.ts
Normal file
253
src/lib/audio/engines/CsoundEngine.ts
Normal file
@ -0,0 +1,253 @@
|
||||
import { Csound } from '@csound/browser';
|
||||
import type { SynthEngine, PitchLock } from './SynthEngine';
|
||||
|
||||
export interface CsoundParameter {
|
||||
channelName: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export abstract class CsoundEngine<T = any> implements SynthEngine<T> {
|
||||
abstract getName(): string;
|
||||
abstract getDescription(): string;
|
||||
abstract getType(): 'generative' | 'sample' | 'input';
|
||||
|
||||
protected abstract getOrchestra(): string;
|
||||
protected abstract getParametersForCsound(params: T): CsoundParameter[];
|
||||
|
||||
abstract randomParams(pitchLock?: PitchLock): T;
|
||||
abstract mutateParams(params: T, mutationAmount?: number, pitchLock?: PitchLock): T;
|
||||
|
||||
async generate(
|
||||
params: T,
|
||||
sampleRate: number,
|
||||
duration: number,
|
||||
pitchLock?: PitchLock
|
||||
): Promise<[Float32Array, Float32Array]> {
|
||||
const orchestra = this.getOrchestra();
|
||||
const csoundParams = this.getParametersForCsound(params);
|
||||
const outputFile = '/output.wav';
|
||||
const csd = this.buildCSD(orchestra, duration, sampleRate, csoundParams, outputFile);
|
||||
|
||||
try {
|
||||
const csound = await Csound();
|
||||
|
||||
if (!csound) {
|
||||
throw new Error('Failed to initialize Csound');
|
||||
}
|
||||
|
||||
await csound.compileCSD(csd);
|
||||
await csound.start();
|
||||
await csound.perform();
|
||||
await csound.cleanup();
|
||||
|
||||
const wavData = await csound.fs.readFile(outputFile);
|
||||
const audioBuffer = await this.parseWavManually(wavData, sampleRate);
|
||||
|
||||
await csound.terminateInstance();
|
||||
|
||||
const leftChannel = new Float32Array(audioBuffer.leftChannel);
|
||||
const rightChannel = new Float32Array(audioBuffer.rightChannel);
|
||||
|
||||
// Apply short fade-in to prevent click at start
|
||||
this.applyFadeIn(leftChannel, rightChannel, sampleRate);
|
||||
|
||||
const peak = this.findPeak(leftChannel, rightChannel);
|
||||
if (peak > 0.001) {
|
||||
const normalizeGain = 0.85 / peak;
|
||||
this.applyGain(leftChannel, rightChannel, normalizeGain);
|
||||
}
|
||||
|
||||
return [leftChannel, rightChannel];
|
||||
} catch (error) {
|
||||
console.error('Csound generation failed:', error);
|
||||
const numSamples = Math.floor(sampleRate * duration);
|
||||
return [new Float32Array(numSamples), new Float32Array(numSamples)];
|
||||
}
|
||||
}
|
||||
|
||||
private parseWavManually(
|
||||
wavData: Uint8Array,
|
||||
expectedSampleRate: number
|
||||
): { leftChannel: Float32Array; rightChannel: Float32Array } {
|
||||
const view = new DataView(wavData.buffer);
|
||||
|
||||
// Check RIFF header
|
||||
const riff = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));
|
||||
if (riff !== 'RIFF') {
|
||||
throw new Error('Invalid WAV file: no RIFF header');
|
||||
}
|
||||
|
||||
// Check WAVE format
|
||||
const wave = String.fromCharCode(view.getUint8(8), view.getUint8(9), view.getUint8(10), view.getUint8(11));
|
||||
if (wave !== 'WAVE') {
|
||||
throw new Error('Invalid WAV file: no WAVE format');
|
||||
}
|
||||
|
||||
// Find fmt chunk
|
||||
let offset = 12;
|
||||
while (offset < wavData.length) {
|
||||
const chunkId = String.fromCharCode(
|
||||
view.getUint8(offset),
|
||||
view.getUint8(offset + 1),
|
||||
view.getUint8(offset + 2),
|
||||
view.getUint8(offset + 3)
|
||||
);
|
||||
const chunkSize = view.getUint32(offset + 4, true);
|
||||
|
||||
if (chunkId === 'fmt ') {
|
||||
const audioFormat = view.getUint16(offset + 8, true);
|
||||
const numChannels = view.getUint16(offset + 10, true);
|
||||
const sampleRate = view.getUint32(offset + 12, true);
|
||||
const bitsPerSample = view.getUint16(offset + 22, true);
|
||||
|
||||
// Find data chunk
|
||||
let dataOffset = offset + 8 + chunkSize;
|
||||
while (dataOffset < wavData.length) {
|
||||
const dataChunkId = String.fromCharCode(
|
||||
view.getUint8(dataOffset),
|
||||
view.getUint8(dataOffset + 1),
|
||||
view.getUint8(dataOffset + 2),
|
||||
view.getUint8(dataOffset + 3)
|
||||
);
|
||||
const dataChunkSize = view.getUint32(dataOffset + 4, true);
|
||||
|
||||
if (dataChunkId === 'data') {
|
||||
const bytesPerSample = bitsPerSample / 8;
|
||||
const numSamples = Math.floor(dataChunkSize / bytesPerSample / numChannels);
|
||||
|
||||
const leftChannel = new Float32Array(numSamples);
|
||||
const rightChannel = new Float32Array(numSamples);
|
||||
|
||||
let audioDataOffset = dataOffset + 8;
|
||||
|
||||
if (bitsPerSample === 16) {
|
||||
// 16-bit PCM
|
||||
for (let i = 0; i < numSamples; i++) {
|
||||
const leftSample = view.getInt16(audioDataOffset, true);
|
||||
leftChannel[i] = leftSample / 32768.0;
|
||||
audioDataOffset += 2;
|
||||
|
||||
if (numChannels > 1) {
|
||||
const rightSample = view.getInt16(audioDataOffset, true);
|
||||
rightChannel[i] = rightSample / 32768.0;
|
||||
audioDataOffset += 2;
|
||||
} else {
|
||||
rightChannel[i] = leftChannel[i];
|
||||
}
|
||||
}
|
||||
} else if (bitsPerSample === 32 && audioFormat === 3) {
|
||||
// 32-bit float
|
||||
for (let i = 0; i < numSamples; i++) {
|
||||
leftChannel[i] = view.getFloat32(audioDataOffset, true);
|
||||
audioDataOffset += 4;
|
||||
|
||||
if (numChannels > 1) {
|
||||
rightChannel[i] = view.getFloat32(audioDataOffset, true);
|
||||
audioDataOffset += 4;
|
||||
} else {
|
||||
rightChannel[i] = leftChannel[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unsupported WAV format: ${bitsPerSample}-bit, format ${audioFormat}`);
|
||||
}
|
||||
|
||||
return { leftChannel, rightChannel };
|
||||
}
|
||||
|
||||
dataOffset += 8 + dataChunkSize;
|
||||
}
|
||||
throw new Error('No data chunk found in WAV file');
|
||||
}
|
||||
|
||||
offset += 8 + chunkSize;
|
||||
}
|
||||
|
||||
throw new Error('No fmt chunk found in WAV file');
|
||||
}
|
||||
|
||||
private buildCSD(
|
||||
orchestra: string,
|
||||
duration: number,
|
||||
sampleRate: number,
|
||||
parameters: CsoundParameter[],
|
||||
outputFile: string
|
||||
): string {
|
||||
const paramInit = parameters
|
||||
.map(p => `chnset ${p.value}, "${p.channelName}"`)
|
||||
.join('\n');
|
||||
|
||||
return `<CsoundSynthesizer>
|
||||
<CsOptions>
|
||||
-W -d -m0 -o ${outputFile}
|
||||
</CsOptions>
|
||||
<CsInstruments>
|
||||
sr = ${sampleRate}
|
||||
ksmps = 64
|
||||
nchnls = 2
|
||||
0dbfs = 1.0
|
||||
|
||||
${paramInit}
|
||||
|
||||
${orchestra}
|
||||
|
||||
</CsInstruments>
|
||||
<CsScore>
|
||||
i 1 0 ${duration}
|
||||
e
|
||||
</CsScore>
|
||||
</CsoundSynthesizer>`;
|
||||
}
|
||||
|
||||
private findPeak(leftChannel: Float32Array, rightChannel: Float32Array): number {
|
||||
let peak = 0;
|
||||
for (let i = 0; i < leftChannel.length; i++) {
|
||||
peak = Math.max(peak, Math.abs(leftChannel[i]), Math.abs(rightChannel[i]));
|
||||
}
|
||||
return peak;
|
||||
}
|
||||
|
||||
private applyGain(
|
||||
leftChannel: Float32Array,
|
||||
rightChannel: Float32Array,
|
||||
gain: number
|
||||
): void {
|
||||
for (let i = 0; i < leftChannel.length; i++) {
|
||||
leftChannel[i] *= gain;
|
||||
rightChannel[i] *= gain;
|
||||
}
|
||||
}
|
||||
|
||||
private applyFadeIn(
|
||||
leftChannel: Float32Array,
|
||||
rightChannel: Float32Array,
|
||||
sampleRate: number
|
||||
): void {
|
||||
const fadeInMs = 5; // 5ms fade-in to prevent clicks
|
||||
const fadeSamples = Math.floor((fadeInMs / 1000) * sampleRate);
|
||||
const actualFadeSamples = Math.min(fadeSamples, leftChannel.length);
|
||||
|
||||
for (let i = 0; i < actualFadeSamples; i++) {
|
||||
const gain = i / actualFadeSamples;
|
||||
leftChannel[i] *= gain;
|
||||
rightChannel[i] *= gain;
|
||||
}
|
||||
}
|
||||
|
||||
protected randomRange(min: number, max: number): number {
|
||||
return min + Math.random() * (max - min);
|
||||
}
|
||||
|
||||
protected randomInt(min: number, max: number): number {
|
||||
return Math.floor(this.randomRange(min, max + 1));
|
||||
}
|
||||
|
||||
protected randomChoice<U>(choices: readonly U[]): U {
|
||||
return choices[Math.floor(Math.random() * choices.length)];
|
||||
}
|
||||
|
||||
protected mutateValue(value: number, amount: number, min: number, max: number): number {
|
||||
const variation = value * amount * (Math.random() * 2 - 1);
|
||||
return Math.max(min, Math.min(max, value + variation));
|
||||
}
|
||||
}
|
||||
338
src/lib/audio/engines/SubtractiveThreeOsc.ts
Normal file
338
src/lib/audio/engines/SubtractiveThreeOsc.ts
Normal file
@ -0,0 +1,338 @@
|
||||
import { CsoundEngine, type CsoundParameter } from './CsoundEngine';
|
||||
import type { PitchLock } from './SynthEngine';
|
||||
|
||||
enum Waveform {
|
||||
Sine = 0,
|
||||
Saw = 1,
|
||||
Square = 2,
|
||||
Triangle = 3,
|
||||
}
|
||||
|
||||
interface OscillatorParams {
|
||||
waveform: Waveform;
|
||||
ratio: number;
|
||||
level: number;
|
||||
attack: number;
|
||||
decay: number;
|
||||
sustain: number;
|
||||
release: number;
|
||||
}
|
||||
|
||||
interface FilterParams {
|
||||
cutoff: number;
|
||||
resonance: number;
|
||||
envAmount: number;
|
||||
attack: number;
|
||||
decay: number;
|
||||
sustain: number;
|
||||
release: number;
|
||||
}
|
||||
|
||||
export interface SubtractiveThreeOscParams {
|
||||
baseFreq: number;
|
||||
osc1: OscillatorParams;
|
||||
osc2: OscillatorParams;
|
||||
osc3: OscillatorParams;
|
||||
filter: FilterParams;
|
||||
stereoWidth: number;
|
||||
}
|
||||
|
||||
export class SubtractiveThreeOsc extends CsoundEngine<SubtractiveThreeOscParams> {
|
||||
getName(): string {
|
||||
return 'Subtractive 3-OSC';
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return 'Three-oscillator subtractive synthesis with resonant filter';
|
||||
}
|
||||
|
||||
getType() {
|
||||
return 'generative' as const;
|
||||
}
|
||||
|
||||
protected getOrchestra(): string {
|
||||
return `
|
||||
instr 1
|
||||
; Get base frequency
|
||||
ibasefreq chnget "basefreq"
|
||||
istereo chnget "stereowidth"
|
||||
|
||||
; Oscillator 1 parameters
|
||||
iosc1wave chnget "osc1_wave"
|
||||
iosc1ratio chnget "osc1_ratio"
|
||||
iosc1level chnget "osc1_level"
|
||||
iosc1attack chnget "osc1_attack"
|
||||
iosc1decay chnget "osc1_decay"
|
||||
iosc1sustain chnget "osc1_sustain"
|
||||
iosc1release chnget "osc1_release"
|
||||
|
||||
; Oscillator 2 parameters
|
||||
iosc2wave chnget "osc2_wave"
|
||||
iosc2ratio chnget "osc2_ratio"
|
||||
iosc2level chnget "osc2_level"
|
||||
iosc2attack chnget "osc2_attack"
|
||||
iosc2decay chnget "osc2_decay"
|
||||
iosc2sustain chnget "osc2_sustain"
|
||||
iosc2release chnget "osc2_release"
|
||||
|
||||
; Oscillator 3 parameters
|
||||
iosc3wave chnget "osc3_wave"
|
||||
iosc3ratio chnget "osc3_ratio"
|
||||
iosc3level chnget "osc3_level"
|
||||
iosc3attack chnget "osc3_attack"
|
||||
iosc3decay chnget "osc3_decay"
|
||||
iosc3sustain chnget "osc3_sustain"
|
||||
iosc3release chnget "osc3_release"
|
||||
|
||||
; Filter parameters
|
||||
ifiltcutoff chnget "filt_cutoff"
|
||||
ifiltres chnget "filt_resonance"
|
||||
ifiltenvamt chnget "filt_envamt"
|
||||
ifiltattack chnget "filt_attack"
|
||||
ifiltdecay chnget "filt_decay"
|
||||
ifiltsustain chnget "filt_sustain"
|
||||
ifiltrelease chnget "filt_release"
|
||||
|
||||
idur = p3
|
||||
|
||||
; Convert ratios to time values
|
||||
iosc1att = iosc1attack * idur
|
||||
iosc1dec = iosc1decay * idur
|
||||
iosc1rel = iosc1release * idur
|
||||
|
||||
iosc2att = iosc2attack * idur
|
||||
iosc2dec = iosc2decay * idur
|
||||
iosc2rel = iosc2release * idur
|
||||
|
||||
iosc3att = iosc3attack * idur
|
||||
iosc3dec = iosc3decay * idur
|
||||
iosc3rel = iosc3release * idur
|
||||
|
||||
ifiltatt = ifiltattack * idur
|
||||
ifiltdec = ifiltdecay * idur
|
||||
ifiltrel = ifiltrelease * idur
|
||||
|
||||
; Stereo detuning
|
||||
idetune = 1 + (istereo * 0.001)
|
||||
ifreqL = ibasefreq / idetune
|
||||
ifreqR = ibasefreq * idetune
|
||||
|
||||
; Oscillator 1 envelopes
|
||||
kenv1 madsr iosc1att, iosc1dec, iosc1sustain, iosc1rel
|
||||
|
||||
; Oscillator 1 - Left
|
||||
if iosc1wave == 0 then
|
||||
aosc1L oscili kenv1 * iosc1level, ifreqL * iosc1ratio
|
||||
elseif iosc1wave == 1 then
|
||||
aosc1L vco2 kenv1 * iosc1level, ifreqL * iosc1ratio, 0
|
||||
elseif iosc1wave == 2 then
|
||||
aosc1L vco2 kenv1 * iosc1level, ifreqL * iosc1ratio, 10
|
||||
else
|
||||
aosc1L vco2 kenv1 * iosc1level, ifreqL * iosc1ratio, 12
|
||||
endif
|
||||
|
||||
; Oscillator 1 - Right
|
||||
if iosc1wave == 0 then
|
||||
aosc1R oscili kenv1 * iosc1level, ifreqR * iosc1ratio
|
||||
elseif iosc1wave == 1 then
|
||||
aosc1R vco2 kenv1 * iosc1level, ifreqR * iosc1ratio, 0
|
||||
elseif iosc1wave == 2 then
|
||||
aosc1R vco2 kenv1 * iosc1level, ifreqR * iosc1ratio, 10
|
||||
else
|
||||
aosc1R vco2 kenv1 * iosc1level, ifreqR * iosc1ratio, 12
|
||||
endif
|
||||
|
||||
; Oscillator 2 envelopes
|
||||
kenv2 madsr iosc2att, iosc2dec, iosc2sustain, iosc2rel
|
||||
|
||||
; Oscillator 2 - Left
|
||||
if iosc2wave == 0 then
|
||||
aosc2L oscili kenv2 * iosc2level, ifreqL * iosc2ratio
|
||||
elseif iosc2wave == 1 then
|
||||
aosc2L vco2 kenv2 * iosc2level, ifreqL * iosc2ratio, 0
|
||||
elseif iosc2wave == 2 then
|
||||
aosc2L vco2 kenv2 * iosc2level, ifreqL * iosc2ratio, 10
|
||||
else
|
||||
aosc2L vco2 kenv2 * iosc2level, ifreqL * iosc2ratio, 12
|
||||
endif
|
||||
|
||||
; Oscillator 2 - Right
|
||||
if iosc2wave == 0 then
|
||||
aosc2R oscili kenv2 * iosc2level, ifreqR * iosc2ratio
|
||||
elseif iosc2wave == 1 then
|
||||
aosc2R vco2 kenv2 * iosc2level, ifreqR * iosc2ratio, 0
|
||||
elseif iosc2wave == 2 then
|
||||
aosc2R vco2 kenv2 * iosc2level, ifreqR * iosc2ratio, 10
|
||||
else
|
||||
aosc2R vco2 kenv2 * iosc2level, ifreqR * iosc2ratio, 12
|
||||
endif
|
||||
|
||||
; Oscillator 3 envelopes
|
||||
kenv3 madsr iosc3att, iosc3dec, iosc3sustain, iosc3rel
|
||||
|
||||
; Oscillator 3 - Left
|
||||
if iosc3wave == 0 then
|
||||
aosc3L oscili kenv3 * iosc3level, ifreqL * iosc3ratio
|
||||
elseif iosc3wave == 1 then
|
||||
aosc3L vco2 kenv3 * iosc3level, ifreqL * iosc3ratio, 0
|
||||
elseif iosc3wave == 2 then
|
||||
aosc3L vco2 kenv3 * iosc3level, ifreqL * iosc3ratio, 10
|
||||
else
|
||||
aosc3L vco2 kenv3 * iosc3level, ifreqL * iosc3ratio, 12
|
||||
endif
|
||||
|
||||
; Oscillator 3 - Right
|
||||
if iosc3wave == 0 then
|
||||
aosc3R oscili kenv3 * iosc3level, ifreqR * iosc3ratio
|
||||
elseif iosc3wave == 1 then
|
||||
aosc3R vco2 kenv3 * iosc3level, ifreqR * iosc3ratio, 0
|
||||
elseif iosc3wave == 2 then
|
||||
aosc3R vco2 kenv3 * iosc3level, ifreqR * iosc3ratio, 10
|
||||
else
|
||||
aosc3R vco2 kenv3 * iosc3level, ifreqR * iosc3ratio, 12
|
||||
endif
|
||||
|
||||
; Mix oscillators
|
||||
amixL = aosc1L + aosc2L + aosc3L
|
||||
amixR = aosc1R + aosc2R + aosc3R
|
||||
|
||||
; Filter envelope
|
||||
kfiltenv madsr ifiltatt, ifiltdec, ifiltsustain, ifiltrel
|
||||
kcutoff = ifiltcutoff + (kfiltenv * ifiltenvamt * 10000)
|
||||
kcutoff = limit(kcutoff, 20, 20000)
|
||||
|
||||
; Apply moogladder filter
|
||||
afiltL moogladder amixL, kcutoff, ifiltres
|
||||
afiltR moogladder amixR, kcutoff, ifiltres
|
||||
|
||||
outs afiltL, afiltR
|
||||
endin
|
||||
`;
|
||||
}
|
||||
|
||||
protected getParametersForCsound(params: SubtractiveThreeOscParams): CsoundParameter[] {
|
||||
return [
|
||||
{ channelName: 'basefreq', value: params.baseFreq },
|
||||
{ channelName: 'stereowidth', value: params.stereoWidth },
|
||||
|
||||
{ channelName: 'osc1_wave', value: params.osc1.waveform },
|
||||
{ channelName: 'osc1_ratio', value: params.osc1.ratio },
|
||||
{ channelName: 'osc1_level', value: params.osc1.level },
|
||||
{ channelName: 'osc1_attack', value: params.osc1.attack },
|
||||
{ channelName: 'osc1_decay', value: params.osc1.decay },
|
||||
{ channelName: 'osc1_sustain', value: params.osc1.sustain },
|
||||
{ channelName: 'osc1_release', value: params.osc1.release },
|
||||
|
||||
{ channelName: 'osc2_wave', value: params.osc2.waveform },
|
||||
{ channelName: 'osc2_ratio', value: params.osc2.ratio },
|
||||
{ channelName: 'osc2_level', value: params.osc2.level },
|
||||
{ channelName: 'osc2_attack', value: params.osc2.attack },
|
||||
{ channelName: 'osc2_decay', value: params.osc2.decay },
|
||||
{ channelName: 'osc2_sustain', value: params.osc2.sustain },
|
||||
{ channelName: 'osc2_release', value: params.osc2.release },
|
||||
|
||||
{ channelName: 'osc3_wave', value: params.osc3.waveform },
|
||||
{ channelName: 'osc3_ratio', value: params.osc3.ratio },
|
||||
{ channelName: 'osc3_level', value: params.osc3.level },
|
||||
{ channelName: 'osc3_attack', value: params.osc3.attack },
|
||||
{ channelName: 'osc3_decay', value: params.osc3.decay },
|
||||
{ channelName: 'osc3_sustain', value: params.osc3.sustain },
|
||||
{ channelName: 'osc3_release', value: params.osc3.release },
|
||||
|
||||
{ channelName: 'filt_cutoff', value: params.filter.cutoff },
|
||||
{ channelName: 'filt_resonance', value: params.filter.resonance },
|
||||
{ channelName: 'filt_envamt', value: params.filter.envAmount },
|
||||
{ channelName: 'filt_attack', value: params.filter.attack },
|
||||
{ channelName: 'filt_decay', value: params.filter.decay },
|
||||
{ channelName: 'filt_sustain', value: params.filter.sustain },
|
||||
{ channelName: 'filt_release', value: params.filter.release },
|
||||
];
|
||||
}
|
||||
|
||||
randomParams(pitchLock?: PitchLock): SubtractiveThreeOscParams {
|
||||
let baseFreq: number;
|
||||
if (pitchLock?.enabled) {
|
||||
baseFreq = pitchLock.frequency;
|
||||
} else {
|
||||
const baseFreqChoices = [55, 82.4, 110, 146.8, 220, 293.7, 440];
|
||||
baseFreq = this.randomChoice(baseFreqChoices) * this.randomRange(0.98, 1.02);
|
||||
}
|
||||
|
||||
const harmonicRatios = [0.5, 1, 2, 3, 4];
|
||||
const detuneRatios = [0.99, 1.0, 1.01, 1.02, 0.98];
|
||||
|
||||
return {
|
||||
baseFreq,
|
||||
osc1: this.randomOscillator(harmonicRatios),
|
||||
osc2: this.randomOscillator(detuneRatios),
|
||||
osc3: this.randomOscillator(harmonicRatios),
|
||||
filter: this.randomFilter(),
|
||||
stereoWidth: this.randomRange(0.2, 0.8),
|
||||
};
|
||||
}
|
||||
|
||||
private randomOscillator(ratios: number[]): OscillatorParams {
|
||||
return {
|
||||
waveform: this.randomInt(0, 3) as Waveform,
|
||||
ratio: this.randomChoice(ratios),
|
||||
level: this.randomRange(0.2, 0.5),
|
||||
attack: this.randomRange(0.001, 0.15),
|
||||
decay: this.randomRange(0.02, 0.25),
|
||||
sustain: this.randomRange(0.3, 0.8),
|
||||
release: this.randomRange(0.05, 0.4),
|
||||
};
|
||||
}
|
||||
|
||||
private randomFilter(): FilterParams {
|
||||
return {
|
||||
cutoff: this.randomRange(200, 5000),
|
||||
resonance: this.randomRange(0.1, 0.8),
|
||||
envAmount: this.randomRange(0.2, 1.2),
|
||||
attack: this.randomRange(0.001, 0.15),
|
||||
decay: this.randomRange(0.05, 0.3),
|
||||
sustain: this.randomRange(0.2, 0.7),
|
||||
release: this.randomRange(0.05, 0.4),
|
||||
};
|
||||
}
|
||||
|
||||
mutateParams(
|
||||
params: SubtractiveThreeOscParams,
|
||||
mutationAmount: number = 0.15,
|
||||
pitchLock?: PitchLock
|
||||
): SubtractiveThreeOscParams {
|
||||
const baseFreq = pitchLock?.enabled ? pitchLock.frequency : params.baseFreq;
|
||||
|
||||
return {
|
||||
baseFreq,
|
||||
osc1: this.mutateOscillator(params.osc1, mutationAmount),
|
||||
osc2: this.mutateOscillator(params.osc2, mutationAmount),
|
||||
osc3: this.mutateOscillator(params.osc3, mutationAmount),
|
||||
filter: this.mutateFilter(params.filter, mutationAmount),
|
||||
stereoWidth: this.mutateValue(params.stereoWidth, mutationAmount, 0, 1),
|
||||
};
|
||||
}
|
||||
|
||||
private mutateOscillator(osc: OscillatorParams, amount: number): OscillatorParams {
|
||||
return {
|
||||
waveform: Math.random() < 0.1 ? (this.randomInt(0, 3) as Waveform) : osc.waveform,
|
||||
ratio: Math.random() < 0.1 ? this.randomChoice([0.5, 0.98, 0.99, 1, 1.01, 1.02, 2, 3, 4]) : osc.ratio,
|
||||
level: this.mutateValue(osc.level, amount, 0.1, 0.7),
|
||||
attack: this.mutateValue(osc.attack, amount, 0.001, 0.3),
|
||||
decay: this.mutateValue(osc.decay, amount, 0.01, 0.4),
|
||||
sustain: this.mutateValue(osc.sustain, amount, 0.1, 0.9),
|
||||
release: this.mutateValue(osc.release, amount, 0.02, 0.6),
|
||||
};
|
||||
}
|
||||
|
||||
private mutateFilter(filter: FilterParams, amount: number): FilterParams {
|
||||
return {
|
||||
cutoff: this.mutateValue(filter.cutoff, amount, 100, 8000),
|
||||
resonance: this.mutateValue(filter.resonance, amount, 0, 0.95),
|
||||
envAmount: this.mutateValue(filter.envAmount, amount, 0, 1.5),
|
||||
attack: this.mutateValue(filter.attack, amount, 0.001, 0.3),
|
||||
decay: this.mutateValue(filter.decay, amount, 0.01, 0.4),
|
||||
sustain: this.mutateValue(filter.sustain, amount, 0.1, 0.9),
|
||||
release: this.mutateValue(filter.release, amount, 0.02, 0.6),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,7 @@ export interface SynthEngine<T = any> {
|
||||
getName(): string;
|
||||
getDescription(): string;
|
||||
getType(): EngineType;
|
||||
generate(params: T, sampleRate: number, duration: number, pitchLock?: PitchLock): [Float32Array, Float32Array];
|
||||
generate(params: T, sampleRate: number, duration: number, pitchLock?: PitchLock): [Float32Array, Float32Array] | Promise<[Float32Array, Float32Array]>;
|
||||
randomParams(pitchLock?: PitchLock): T;
|
||||
mutateParams(params: T, mutationAmount?: number, pitchLock?: PitchLock): T;
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import { BassDrum } from './BassDrum';
|
||||
import { HiHat } from './HiHat';
|
||||
import { ParticleNoise } from './ParticleNoise';
|
||||
import { DustNoise } from './DustNoise';
|
||||
import { SubtractiveThreeOsc } from './SubtractiveThreeOsc';
|
||||
|
||||
export const engines: SynthEngine[] = [
|
||||
new Sample(),
|
||||
@ -35,4 +36,5 @@ export const engines: SynthEngine[] = [
|
||||
new AdditiveEngine(),
|
||||
new ParticleNoise(),
|
||||
new DustNoise(),
|
||||
new SubtractiveThreeOsc(),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user