From 5602bbd2a49f5250134916c9b22444d69d2dcc94 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sat, 25 May 2024 17:32:07 -0700 Subject: [PATCH 01/40] Create Tune-Shark-V3.js --- extensions/SharkPool/Tune-Shark-V3.js | 910 ++++++++++++++++++++++++++ 1 file changed, 910 insertions(+) create mode 100644 extensions/SharkPool/Tune-Shark-V3.js diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js new file mode 100644 index 0000000000..cfd114d40d --- /dev/null +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -0,0 +1,910 @@ +// Name: Tune Shark V3 +// ID: SPtuneShark3 +// Description: Advanced Audio Engine, giving Complex Sound Control +// By: SharkPool + +// Version V.3.0.01 +// Credit to HOME for the song "Resonance" being used as the default audio link + +(function (Scratch) { + "use strict"; + if (!Scratch.extensions.unsandboxed) throw new Error("Tune Shark V3 must be run unsandboxed"); + + const vm = Scratch.vm; + const runtime = vm.runtime; + + const menuIconURI = +"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMDIuMTg1MTgiIGhlaWdodD0iMTAyLjE4NTE4IiB2aWV3Qm94PSIwLDAsMTAyLjE4NTE4LDEwMi4xODUxOCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE4OC45MDc0MSwtMTI4LjkwNzQxKSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTg4LjkwNzQxLDE4MGMwLC0yOC4yMTc2NiAyMi44NzQ5MywtNTEuMDkyNTkgNTEuMDkyNTksLTUxLjA5MjU5YzI4LjIxNzY2LDAgNTEuMDkyNTksMjIuODc0OTMgNTEuMDkyNTksNTEuMDkyNTljMCwyOC4yMTc2NiAtMjIuODc0OTMsNTEuMDkyNTkgLTUxLjA5MjU5LDUxLjA5MjU5Yy0yOC4yMTc2NiwwIC01MS4wOTI1OSwtMjIuODc0OTMgLTUxLjA5MjU5LC01MS4wOTI1OXoiIGZpbGw9IiM0MDQwNDAiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTE5My43Njc0NCwxODBjMCwtMjUuNTMzNTQgMjAuNjk5MDIsLTQ2LjIzMjU2IDQ2LjIzMjU2LC00Ni4yMzI1NmMyNS41MzM1NCwwIDQ2LjIzMjU2LDIwLjY5OTAyIDQ2LjIzMjU2LDQ2LjIzMjU2YzAsMjUuNTMzNTQgLTIwLjY5OTAyLDQ2LjIzMjU2IC00Ni4yMzI1Niw0Ni4yMzI1NmMtMjUuNTMzNTQsMCAtNDYuMjMyNTYsLTIwLjY5OTAyIC00Ni4yMzI1NiwtNDYuMjMyNTZ6IiBmaWxsPSIjNjY2NjY2IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjxwYXRoIGQ9Ik0yNjEuNzQzNjIsMjE1LjU0OTY4bC0wLjAwMDQ2LC0wLjAwMjJjLTEuMjUyOTUsMi41MzUzOSAtNC42NjcsMy45MzQxMiAtOC4yNjMsMy4xOTY5OWMtNC4xMTgxNiwtMC44NDQ1NSAtNi45MTQ5NCwtNC4xNzAzNiAtNi4yNDY0NiwtNy40Mjg3OGMwLjY2ODI1LC0zLjI1OTA0IDQuNTQ4MDQsLTUuMjE1MzYgOC42NjYyMiwtNC4zNzA4MWMxLjg3NTMyLDAuMzg0MzggMy40NzQ0NSwxLjI4Mzk1IDQuNTk5MjYsMi40NTY2M2w2LjY4MzUzLC0xNC4xNzcwNGMtMTAuNTY1MzEsLTQuMTM1MDUgLTE5Ljc2MDIsLTUuMDk2NjQgLTE5Ljc2MDIsLTUuMDk2NjRsLTguOTg2MTEsMTkuMDYxMjRjLTAuOTY2NjUsMi45MjY5MiAtNC42MzY2MSw0LjYyMTY4IC04LjUxOTY3LDMuODI1ODJjLTQuMTE3NzUsLTAuODQ0MzYgLTYuOTE0MzMsLTQuMTcwNTcgLTYuMjQ2MjcsLTcuNDI5MTljMC42NjgwNiwtMy4yNTg2MyA0LjU0Nzg1LC01LjIxNDk2IDguNjY2MjIsLTQuMzcwODFjMS45MTAyOSwwLjM5MTQyIDMuNTM2MTksMS4zMTc1MyA0LjY2NDE1LDIuNTIyNTNsMTIuMDM0OCwtMjUuNTA3MDJjMCwwIDEzLjUzMjg1LDAuMjM1NiAyNi45NDc4Niw3LjExMzc5eiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTIxNy4yMTYyNSwxNTkuNzM4MzJsNC4wNDI1NiwyMy42ODM0NyIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjxwYXRoIGQ9Ik0yMjIuOTM5OTYsMTg0LjEyNTU1YzEuMzgwOTIsMi42MDY0NiAtMC43NzE4NCw2LjA0MDQ2IC00LjgwODMzLDcuNjcwMDVjLTQuMDM2NDksMS42Mjk1OSAtOC40MjgxNywwLjgzNzY2IC05LjgwOTEsLTEuNzY4OGMtMS4zODA5MiwtMi42MDY0NiAwLjc3MTg0LC02LjA0MDQ2IDQuODA4MzMsLTcuNjcwMDVjNC4wMzY0OSwtMS42Mjk1OSA4LjQyODE3LC0wLjgzNzY2IDkuODA5MSwxLjc2ODh6IiBmaWxsPSIjZmZmZmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTIxNC45ODk2MywxNTcuMDU4NGMwLDAgOS43MDU4LC0xLjcwNDcyIDEyLjM2MjkxLDIuNzY5OTdjMi4zNzUxMiw0LjAwMDA4IDAuMDcxNTUsOC42OTQxOCAwLjIxMjA5LDEwLjEyNzMxYzAuMTQwNTQsMS40MzMxMyAyLjE3NTAzLDEuMTA4OTYgMi4xNzUwMywxLjEwODk2bC0wLjAxMzI2LDEuNjc0MTFjMCwwIC0yLjg2NDQ3LDAuNjY5MDIgLTMuNDExOTksLTEuMzE5MTVjLTAuNTQ3NTMsLTEuOTg4MTYgLTAuNTU5NTIsLTUuOTc4MjkgLTIuODIzNTQsLTguMjI3NzdjLTIuMjY0NjYsLTIuMjQ5MjcgLTcuNTM2ODcsLTAuNzU4NjkgLTcuNTM2ODcsLTAuNzU4NjkiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTI0OS4zNDMxMywxNjQuMjc4MDdjLTAuMzMzNTEsMi4xMTc2MSAtMi4zNTI4NywzLjMwODgxIC00LjE4OTA2LDQuMDAxNThjLTEuNjQ5NzQsMC43MjYxIC0zLjU0OTg5LDEuMDA4MDIgLTUuMjc1NjUsMC4zNjk5Yy0xLjYzMTksLTAuMzgwOTkgLTMuMTg4MjEsLTEuODA2NzYgLTIuOTk0NiwtMy41OTczM2MwLjE1NDQzLC0yLjA4NTA5IDEuODk2NTksLTMuODIyNDMgMy44NDIzOCwtNC40MDQ5OGMxLjkzOTE1LC0wLjc5OTIyIDQuMjUyODcsLTAuNzMwNDYgNi4xMDQ2MSwwLjI1ODgzYzAuOTEyNywwLjQyMjE4IDIuMTg5MDgsLTE0LjE0MDYzIDMuMDM1MDksLTIwLjU4MTk4YzAuMTAwNDksLTAuNjQxOCAyLjU4Mzk4LC0wLjQ3MDkzIDIuNTEyOTMsMC4xNTEwNWMwLDAgLTEuODUzNTQsMTUuNTIyMyAtMy4wMzU3MSwyMy44MDI5MnoiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvZz48L2c+PC9zdmc+"; + const blockIconURI = +"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMjk5LC0xNDAuNTEyOTkpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTIwMC41MTMsMjE5LjQ4NzAxdi03OC45NzQwMWg3OC45NzQwMXY3OC45NzQwMXoiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIvPjxwYXRoIGQ9Ik0yNjEuNzE1MiwxOTQuMzQ4NjZsLTAuMDAxNzcsLTAuMDAyMzZjLTAuMDY4NDQsMy43MDkzNSAtMy4zMzcwNiw3LjI3ODg3IC04LjAxNjM4LDguNDE1ODFjLTUuMzU5LDEuMzAxNTUgLTEwLjUzODA1LC0xLjA4MDMgLTExLjU2NzYxLC01LjMyMDY1Yy0xLjAzMDE1LC00LjI0MDk1IDIuNDc5MTksLTguNzMyNjQgNy44MzgyLC0xMC4wMzQxOWMyLjQ0MDI1LC0wLjU5Mjk1IDQuODQwOTgsLTAuNDIwMDggNi44MzE2NSwwLjM0MjJ2LTIwLjU2MTAzYy0xNC44NDk4LDEuMDAzNiAtMjYuMjk4MjMsNS4wMDYxOCAtMjYuMjk4MjMsNS4wMDYxOHYyNy42NDQ2MmMwLjQ5MDI5LDQuMDEzNzkgLTIuOTE2MzksOC4wNzc3NCAtNy45NjkxOCw5LjMwNTU0Yy01LjM1ODQxLDEuMzAxNTUgLTEwLjUzNzQ2LC0xLjA4MDg5IC0xMS41Njc2MSwtNS4zMjEyNGMtMS4wMzAxNSwtNC4yNDAzNiAyLjQ3OTE5LC04LjczMjA1IDcuODM4MiwtMTAuMDM0MTljMi40ODU2OCwtMC42MDQxNiA0LjkzMzAyLC0wLjQxNDc3IDYuOTQ1NTIsMC4zODQwOWwwLjAxMTgsLTM2Ljk5ODUzYzAsMCAxNi4xODk3LC03LjI5MDY3IDM1Ljk1NTQxLC02LjYzMzQxeiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+"; + + const groupIconURI = +"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMywtMTQwLjUxMzAxKSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjAwLjUxMywyMTkuNDg3MDJ2LTc4Ljk3NDAxaDc4Ljk3NDAxdjc4Ljk3NDAxeiIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiLz48cGF0aCBkPSJNMjY1Ljk1NzcxLDE4My4yMzU2NmwtMC4wMDEwOCwtMC4wMDIxNmMtMC42MDQwNywyLjk3OTU0IC0zLjc2Njc0LDUuMzcyODIgLTcuNzA2NDMsNS41OTY3MmMtNC41MTE4NiwwLjI1NiAtOC4zMzM2MSwtMi40MzAxMiAtOC41MzU5MywtNi4wMDAxMmMtMC4yMDI3MSwtMy41NzA1NyAzLjI5MDQ0LC02LjY3MTQ4IDcuODAyMzEsLTYuOTI3NDhjMi4wNTQ1NCwtMC4xMTY4IDMuOTYzOSwwLjM3Nzc5IDUuNDU1NTUsMS4yODY3NWwzLjA0MjYyLC0xNi41NzE4MWMtMTIuMTE3MTgsLTEuMzg4NTkgLTIxLjkzNjcxLDAuMTQzMjggLTIxLjkzNjcxLDAuMTQzMjhsLTQuMDkwODUsMjIuMjgxMDZjLTAuMTk4OCwzLjMwNzU5IC0zLjU0NTksNi4wNzg5NCAtNy44MDAwNSw2LjMyMDgxYy00LjUxMTM4LDAuMjU2MDkgLTguMzMzMDUsLTIuNDMwNTEgLTguNTM1ODQsLTYuMDAwNmMtMC4yMDI3OSwtMy41NzAwOSAzLjI5MDM1LC02LjY3MSA3LjgwMjMxLC02LjkyNzQ4YzIuMDkyODIsLTAuMTE5MTEgNC4wMzczLDAuMzk1NjkgNS41NDExMiwxLjMzNzM3bDUuNDg0NTUsLTI5LjgxODM5YzAsMCAxNC4xMjc0NywtMy40ODAzOSAyOS45NjEwMSwtMC4wMjU3MnoiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2luZGV4JnF1b3Q7Om51bGx9IiBmaWxsPSIjZmZmZmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjxwYXRoIGQ9Ik0yMTYuNzI2NDQsMjA2LjAzNzAxYy0zLjQxNjc5LC0xNC45NjcwOSAtOS4xNTg2NiwtNDMuMDc4MzQgLTkuMTU4NjYsLTQzLjA3ODM0Yy0wLjIyNDE5LC0xLjEyNjkgNC4yOTI1MiwtMS44MTU2OCA0LjU3MzU1LC0wLjY1Njk4YzIuNTIzMjYsMTEuNjUzNzggNy4wNjQwOSwzOC4wOTc0NiA4LjY2OTU2LDM3LjE4Njk3YzMuMjM3MTcsLTIuMDkwMDUgNy40NTg4MSwtMi41NjYzMyAxMS4xMjY4MiwtMS4zOTgyNWMzLjY0NzMxLDAuNzcwNzcgNy4wOTcxMywzLjY4NDY0IDcuNjk1NDcsNy40NzUxMWMwLjYyNTM4LDMuMjQ1ODMgLTIuMDA1MjksNi4wODk0OSAtNC45MzI0OSw3LjAzMzU5Yy0zLjA1OTk0LDEuNDI4NjMgLTYuNTc4MjMsMS4yMDA4MyAtOS43MDU3OSwwLjEyMjY0Yy0zLjQ2MzU0LC0wLjk4ODk5IC03LjMzNzY1LC0yLjg2MTkxIC04LjI2ODQ4LC02LjY4NDczeiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvZz48L2c+PC9zdmc+"; + const settingsIconURI = +"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMjk5LC0xNDAuNTEyOTgpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjY1LjA0NTY5LDE4My4xMjI3MWwyLjA0MiwwLjg1NTIxYzIuNTU3MTgsMS4wNzA5NyAzLjc2MTk4LDQuMDEyMTggMi42OTEwMSw2LjU2OTM2bC0xLjM3MTM0LDMuMjc0MzhjLTEuMDcwOTcsMi41NTcxOCAtNC4wMTIxOCwzLjc2MiAtNi41NjkzNiwyLjY5MTAzbC0yLjA0MiwtMC44NTUyMWMtMS4yNjc5NiwxLjYwNDkgLTIuNzEzMzcsMy4wMzAxMiAtNC4yOTQ4Nyw0LjI1ODcxbDAuODM5OTUsMi4wNTA1MmMxLjA1MDkxLDIuNTY1NDkgLTAuMTc2OSw1LjQ5NzE2IC0yLjc0MjQsNi41NDgwN2wtMy4yODUwMywxLjM0NTY1Yy0yLjU2NTQ5LDEuMDUwOTEgLTUuNDk3MTYsLTAuMTc2OSAtNi41NDgwNywtMi43NDIzOWwtMC44Mzk5NiwtMi4wNTA1MmMtMS45ODg5MiwwLjIzMzk4IC00LjAxODgxLDAuMjMyMzYgLTYuMDQ4MzEsLTAuMDIxODFsLTAuODU1MiwyLjA0MTk5Yy0xLjA3MDk3LDIuNTU3MTggLTQuMDEyMTgsMy43NjE5OCAtNi41NjkzNiwyLjY5MTAxbC0zLjI3NDM4LC0xLjM3MTM0Yy0yLjU1NzE4LC0xLjA3MDk3IC0zLjc2MiwtNC4wMTIxNyAtMi42OTEwMywtNi41NjkzNWwwLjg1NTIxLC0yLjA0MTk5Yy0xLjYwNDksLTEuMjY3OTYgLTMuMDMwMTMsLTIuNzEzMzcgLTQuMjU4NzEsLTQuMjk0ODdsLTIuMDUwNTIsMC44Mzk5NWMtMi41NjU0OSwxLjA1MDkxIC01LjQ5NzE2LC0wLjE3NjkgLTYuNTQ4MDcsLTIuNzQyMzlsLTEuMzQ1NjYsLTMuMjg1MDNjLTEuMDUwOTEsLTIuNTY1NDkgMC4xNzY5LC01LjQ5NzE3IDIuNzQyNCwtNi41NDgwOGwyLjA1MDUyLC0wLjgzOTk2Yy0wLjIzMzk3LC0xLjk4ODkyIC0wLjIzMjM2LC00LjAxODggMC4wMjE4MSwtNi4wNDgzbC0yLjA0MiwtMC44NTUyMWMtMi41NTcxOCwtMS4wNzA5NyAtMy43NjE5OCwtNC4wMTIxOCAtMi42OTEwMSwtNi41NjkzNmwxLjM3MTM1LC0zLjI3NDM4YzEuMDcwOTcsLTIuNTU3MTggNC4wMTIxNywtMy43NjIgNi41NjkzNSwtMi42OTEwM2wyLjA0MiwwLjg1NTIxYzEuMjY3OTYsLTEuNjA0OSAyLjcxMzM4LC0zLjAzMDEyIDQuMjk0ODgsLTQuMjU4NzFsLTAuODM5OTYsLTIuMDUwNTJjLTEuMDUwOTEsLTIuNTY1NDkgMC4xNzY5LC01LjQ5NzE2IDIuNzQyNCwtNi41NDgwN2wzLjI4NTAzLC0xLjM0NTY1YzIuNTY1NDksLTEuMDUwOTEgNS40OTcxNiwwLjE3NjkgNi41NDgwNywyLjc0MjM5bDAuODM5OTYsMi4wNTA1MmMxLjk4ODkyLC0wLjIzMzk3IDQuMDE4ODEsLTAuMjMyMzYgNi4wNDgzMSwwLjAyMTgxbDAuODU1MjEsLTIuMDQyYzEuMDcwOTcsLTIuNTU3MTggNC4wMTIxOCwtMy43NjE5OCA2LjU2OTM2LC0yLjY5MTAxbDMuMjc0MzgsMS4zNzEzNGMyLjU1NzE4LDEuMDcwOTcgMy43NjIsNC4wMTIxNyAyLjY5MTAzLDYuNTY5MzVsLTAuODU1MjEsMi4wNDJjMS42MDQ5LDEuMjY3OTYgMy4wMzAxMywyLjcxMzM3IDQuMjU4NzEsNC4yOTQ4N2wyLjA1MDUyLC0wLjgzOTk1YzIuNTY1NDksLTEuMDUwOTEgNS40OTcxNywwLjE3NjkgNi41NDgwOCwyLjc0MjM5bDEuMzQ1NjUsMy4yODUwM2MxLjA1MDkxLDIuNTY1NDkgLTAuMTc2OSw1LjQ5NzE2IC0yLjc0MjQsNi41NDgwN2wtMi4wNTA1MiwwLjgzOTk2YzAuMjMzOTcsMS45ODg5MiAwLjIzMjM2LDQuMDE4OCAtMC4wMjE4MSw2LjA0ODN6TTIyNy41NDgyMiwxNzQuNzg1MDdjLTIuODgwMTMsNi44NzY5NSAwLjM1OTkyLDE0Ljc4NjYyIDcuMjM2ODYsMTcuNjY2NzVjNi44NzY5NSwyLjg4MDEzIDE0Ljc4NjYyLC0wLjM1OTkyIDE3LjY2Njc1LC03LjIzNjg2YzIuODgwMTMsLTYuODc2OTUgLTAuMzU5OTIsLTE0Ljc4NjYyIC03LjIzNjg2LC0xNy42NjY3NWMtNi44NzY5NSwtMi44ODAxMyAtMTQuNzg2NjIsMC4zNTk5MiAtMTcuNjY2NzUsNy4yMzY4NnoiIGZpbGw9IiNmZmZmZmYiLz48cGF0aCBkPSJNMjAwLjUxMywyMTkuNDg3di03OC45NzQwMWg3OC45NzQwMXY3OC45NzQwMXoiIGZpbGw9Im5vbmUiLz48L2c+PC9nPjwvc3ZnPg=="; + const nobIconURI = +"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMjk5LC0xNDAuNTEyOTkpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjAwLjUxMywyMTkuNDg3MDF2LTc4Ljk3NDAxaDc4Ljk3NDAxdjc4Ljk3NDAxeiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0yMzguNDc0MjEsMTUxLjM2MDY3Yy0xLjE2MjMxLDAgLTIuMTA0NTQsLTAuOTQyMjQgLTIuMTA0NTQsLTIuMTA0NTR2LTMuMDUxNTljMCwtMS4xNjIzMSAwLjk0MjI0LC0yLjEwNDU1IDIuMTA0NTQsLTIuMTA0NTVoMy4wNTE1OWMxLjE2MjMxLDAgMi4xMDQ1NCwwLjk0MjI0IDIuMTA0NTQsMi4xMDQ1NXYzLjA1MTU5YzAsMS4xNjIzMSAtMC45NDIyNCwyLjEwNDU0IC0yLjEwNDU0LDIuMTA0NTR6IiBmaWxsPSIjZmZmZmZmIi8+PHBhdGggZD0iTTIzOC40NzQyMSwyMTUuOWMtMS4xNjIzMSwwIC0yLjEwNDU0LC0wLjk0MjI0IC0yLjEwNDU0LC0yLjEwNDU0di0zLjA1MTU5YzAsLTEuMTYyMzEgMC45NDIyNCwtMi4xMDQ1NCAyLjEwNDU0LC0yLjEwNDU0aDMuMDUxNTljMS4xNjIzMSwwIDIuMTA0NTQsMC45NDIyMyAyLjEwNDU0LDIuMTA0NTR2My4wNTE1OWMwLDEuMTYyMzEgLTAuOTQyMjQsMi4xMDQ1NCAtMi4xMDQ1NCwyLjEwNDU0eiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yNTkuMTcyMTYsMTU4LjY3MDA0Yy0wLjgyMTg3LC0wLjgyMTg4IC0wLjgyMTg3LC0yLjE1NDM5IDAsLTIuOTc2MjdsMi4xNTc4LC0yLjE1NzhjMC44MjE4NywtMC44MjE4OCAyLjE1NDM5LC0wLjgyMTg4IDIuOTc2MjcsMGwyLjE1NzgsMi4xNTc4YzAuODIxODcsMC44MjE4OCAwLjgyMTg3LDIuMTU0MzkgMCwyLjk3NjI3bC0yLjE1NzgsMi4xNTc4Yy0wLjgyMTg3LDAuODIxODggLTIuMTU0MzksMC44MjE4OCAtMi45NzYyNywweiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yMTMuNTM1OTcsMjA0LjMwNjIzYy0wLjgyMTg4LC0wLjgyMTg4IC0wLjgyMTg4LC0yLjE1NDM5IDAsLTIuOTc2MjdsMi4xNTc4LC0yLjE1NzhjMC44MjE4OCwtMC44MjE4OCAyLjE1NDM5LC0wLjgyMTg4IDIuOTc2MjcsMGwyLjE1NzgsMi4xNTc4YzAuODIxODgsMC44MjE4OCAwLjgyMTg4LDIuMTU0MzkgMCwyLjk3NjI3bC0yLjE1NzgsMi4xNTc4Yy0wLjgyMTg4LDAuODIxODggLTIuMTU0MzksMC44MjE4OCAtMi45NzYyNywweiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yNjguNjM5MzMsMTc4LjQ3NDJjMCwtMS4xNjIzMSAwLjk0MjI0LC0yLjEwNDUzIDIuMTA0NTQsLTIuMTA0NTNoMy4wNTE1OWMxLjE2MjMxLDAgMi4xMDQ1NCwwLjk0MjIzIDIuMTA0NTQsMi4xMDQ1M3YzLjA1MTZjMCwxLjE2MjMxIC0wLjk0MjIzLDIuMTA0NTMgLTIuMTA0NTQsMi4xMDQ1M2gtMy4wNTE1OWMtMS4xNjIzMSwwIC0yLjEwNDU0LC0wLjk0MjIzIC0yLjEwNDU0LC0yLjEwNDUzeiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yMDQuMSwxNzguNDc0MmMwLC0xLjE2MjMxIDAuOTQyMjQsLTIuMTA0NTMgMi4xMDQ1NCwtMi4xMDQ1M2gzLjA1MTU5YzEuMTYyMzEsMCAyLjEwNDU0LDAuOTQyMjMgMi4xMDQ1NCwyLjEwNDUzdjMuMDUxNmMwLDEuMTYyMzEgLTAuOTQyMjMsMi4xMDQ1MyAtMi4xMDQ1NCwyLjEwNDUzaC0zLjA1MTU5Yy0xLjE2MjMxLDAgLTIuMTA0NTQsLTAuOTQyMjMgLTIuMTA0NTQsLTIuMTA0NTN6IiBmaWxsPSIjZmZmZmZmIi8+PHBhdGggZD0iTTI2MS4zMjk5NywxOTkuMTcyMTZjMC44MjE4NywtMC44MjE4OCAyLjE1NDM5LC0wLjgyMTg4IDIuOTc2MjcsMGwyLjE1NzgsMi4xNTc4YzAuODIxODcsMC44MjE4OCAwLjgyMTg3LDIuMTU0MzkgMCwyLjk3NjI3bC0yLjE1NzgsMi4xNTc4Yy0wLjgyMTg3LDAuODIxODggLTIuMTU0MzksMC44MjE4OCAtMi45NzYyNywwbC0yLjE1NzgsLTIuMTU3OGMtMC44MjE4NywtMC44MjE4OCAtMC44MjE4NywtMi4xNTQzOSAwLC0yLjk3NjI3eiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yMTUuNjkzNzcsMTUzLjUzNTk3YzAuODIxODgsLTAuODIxODggMi4xNTQzOSwtMC44MjE4OCAyLjk3NjI3LDBsMi4xNTc4LDIuMTU3OGMwLjgyMTg4LDAuODIxODggMC44MjE4OCwyLjE1NDM5IDAsMi45NzYyN2wtMi4xNTc4LDIuMTU3OGMtMC44MjE4OCwwLjgyMTg4IC0yLjE1NDM5LDAuODIxODggLTIuOTc2MjcsMGwtMi4xNTc4LC0yLjE1NzhjLTAuODIxODgsLTAuODIxODggLTAuODIxODgsLTIuMTU0MzkgMCwtMi45NzYyN3oiIGZpbGw9IiNmZmZmZmYiLz48cGF0aCBkPSJNMjI0LjEwOTEyLDE2My45NjYyMmM3LjU0NzAyLC03LjU0NzAyIDE5LjA2NTQyLC04LjcwMjI0IDI3LjgyODIzLC0zLjQ2NTYzYy0wLjEyNTAxLDAuMDkwMSAtMTMuMjY0MjYsMTcuODcyNDcgLTEyLjEzMzc5LDE5LjAwMjkzbDEuMDM3OTIsMS4wMzc5MmMxLjEzMDQ3LDEuMTMwNDcgMTguOTEyODMsLTEyLjAwODc4IDE5LjAwMjkzLC0xMi4xMzM3OWM1LjIzNjYxLDguNzYyODEgNC4wODE0LDIwLjI4MTIgLTMuNDY1NjMsMjcuODI4MjNjLTguOTExMDIsOC45MTEwMiAtMjMuMzU4NjUsOC45MTEwMiAtMzIuMjY5NjcsMGMtOC45MTEwMiwtOC45MTEwMiAtOC45MTEwMiwtMjMuMzU4NjQgMCwtMzIuMjY5NjZ6IiBmaWxsPSIjZmZmZmZmIi8+PC9nPjwvZz48L3N2Zz4="; + + const stopSign = +"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNCAxNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTQgMTQ7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRUM1OTU5O3N0cm9rZTojQjg0ODQ4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9Cjwvc3R5bGU+Cjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNC4zLDAuNSA5LjcsMC41IDEzLjUsNC4zIDEzLjUsOS43IDkuNywxMy41IDQuMywxMy41IDAuNSw5LjcgMC41LDQuMyAiLz4KPC9zdmc+Cg=="; + const startFlag = +"data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48ZGVmcz48c3R5bGU+LmNscy0xLC5jbHMtMntmaWxsOiM0Y2JmNTY7c3Ryb2tlOiM0NTk5M2Q7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO30uY2xzLTJ7c3Ryb2tlLXdpZHRoOjEuNXB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tZ3JlZW4tZmxhZzwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNLjc1LDJBNi40NCw2LjQ0LDAsMCwxLDguNDQsMmgwYTYuNDQsNi40NCwwLDAsMCw3LjY5LDBWMTIuNGE2LjQ0LDYuNDQsMCwwLDEtNy42OSwwaDBhNi40NCw2LjQ0LDAsMCwwLTcuNjksMCIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjAuNzUiIHkxPSIxNi43NSIgeDI9IjAuNzUiIHkyPSIwLjc1Ii8+PC9zdmc+"; + + // Pizzicato Library (Basically Web Audio API, but with Premade Effects, Stuff) + const scriptElement = document.createElement("script"); + scriptElement.textContent = +`!function(e){"use strict";function t(e,t){this.options={},e=e||this.options;var i={frequency:350,peak:1};this.inputNode=this.filterNode=s.context.createBiquadFilter(),this.filterNode.type=t,this.outputNode=o.context.createGain(),this.filterNode.connect(this.outputNode);for(var n in i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]}function i(){var e,t,i=s.context.sampleRate*this.time,n=o.context.createBuffer(2,i,s.context.sampleRate),a=n.getChannelData(0),r=n.getChannelData(1);for(t=0;i>t;t++)e=this.reverse?i-t:t,a[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay),r[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay);this.reverbNode.buffer=n}function n(e){for(var t=s.context.sampleRate,i=new Float32Array(t),n=Math.PI/180,o=0;t>o;o++){var a=2*o/t-1;i[o]=(3+e)*a*20*n/(Math.PI+e*Math.abs(a))}return i}var o={},s=o,a="object"==typeof module&&module.exports,r="function"==typeof define&&define.amd;a?module.exports=o:r?define([],o):e.Pizzicato=e.Pz=o;var c=e.AudioContext||e.webkitAudioContext;if(!c)return void console.error("No AudioContext found in this environment. Please ensure your window or global object contains a working AudioContext constructor function.");o.context=new c;var h=o.context.createGain();h.connect(o.context.destination),o.Util={isString:function(e){return"[object String]"===toString.call(e)},isObject:function(e){return"[object Object]"===toString.call(e)},isFunction:function(e){return"[object Function]"===toString.call(e)},isNumber:function(e){return"[object Number]"===toString.call(e)&&e===+e},isArray:function(e){return"[object Array]"===toString.call(e)},isInRange:function(e,t,i){return s.Util.isNumber(e)&&s.Util.isNumber(t)&&s.Util.isNumber(i)?e>=t&&i>=e:!1},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof s.Sound},isEffect:function(e){for(var t in o.Effects)if(e instanceof o.Effects[t])return!0;return!1},normalize:function(e,t,i){return s.Util.isNumber(e)&&s.Util.isNumber(t)&&s.Util.isNumber(i)?(i-t)*e/1+t:void 0},getDryLevel:function(e){return!s.Util.isNumber(e)||e>1||0>e?0:.5>=e?1:1-2*(e-.5)},getWetLevel:function(e){return!s.Util.isNumber(e)||e>1||0>e?0:e>=.5?1:1-2*(.5-e)}};var u=o.context.createGain(),d=Object.getPrototypeOf(Object.getPrototypeOf(u)),l=d.connect;d.connect=function(e){var t=s.Util.isEffect(e)?e.inputNode:e;return l.call(this,t),e},Object.defineProperty(o,"volume",{enumerable:!0,get:function(){return h.gain.value},set:function(e){s.Util.isInRange(e,0,1)&&h&&(h.gain.value=e)}}),Object.defineProperty(o,"masterGainNode",{enumerable:!1,get:function(){return h},set:function(e){console.error("Can't set the master gain node")}}),o.Events={on:function(e,t,i){if(e&&t){this._events=this._events||{};var n=this._events[e]||(this._events[e]=[]);n.push({callback:t,context:i||this,handler:this})}},trigger:function(e){if(e){var t,i,n,o;if(this._events=this._events||{},t=this._events[e]||(this._events[e]=[])){for(i=Math.max(0,arguments.length-1),n=[],o=0;i>o;o++)n[o]=arguments[o+1];for(o=0;o1?(e.shift(),void a(e,t)):(i=i||new Error("Error decoding audio file "+e[0]),void(d.isFunction(t)&&t(i)))}.bind(u))},i.onreadystatechange=function(t){4===i.readyState&&200!==i.status&&console.error("Error while fetching "+e[0]+". "+i.statusText)},i.send()}function r(e,t){return navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,navigator.getUserMedia?void navigator.getUserMedia({audio:!0},function(e){u.getRawSourceNode=function(){return o.context.createMediaStreamSource(e)},d.isFunction(t)&&t()}.bind(u),function(e){d.isFunction(t)&&t(e)}):void console.error("Your browser does not support getUserMedia")}function c(e,t){var i=d.isFunction(e)?e:e.audioFunction,n=d.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!n)try{o.context.createScriptProcessor()}catch(s){n=2048}this.getRawSourceNode=function(){var e=o.context.createScriptProcessor(n,1,1);return e.onaudioprocess=i,e}}function h(e,t){this.getRawSourceNode=e.sound.getRawSourceNode,e.sound.sourceNode&&s.Util.isOscillator(e.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=e.sound.frequency)}var u=this,d=o.Util,l=i(e),f=d.isObject(e)&&d.isObject(e.options),p=.04,v=.04;if(l)throw console.error(l),new Error("Error initializing Pizzicato Sound: "+l);this.detached=f&&e.options.detached,this.masterVolume=o.context.createGain(),this.fadeNode=o.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(o.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=f&&e.options.loop,this.attack=f&&d.isNumber(e.options.attack)?e.options.attack:p,this.volume=f&&d.isNumber(e.options.volume)?e.options.volume:1,f&&d.isNumber(e.options.release)?this.release=e.options.release:f&&d.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=v,e?d.isString(e)?a.bind(this)(e,t):d.isFunction(e)?c.bind(this)(e,t):"file"===e.source?a.bind(this)(e.options.path,t):"wave"===e.source?n.bind(this)(e.options,t):"input"===e.source?r.bind(this)(e,t):"script"===e.source?c.bind(this)(e.options,t):"sound"===e.source&&h.bind(this)(e.options,t):n.bind(this)({},t)},o.Sound.prototype=Object.create(o.Events,{play:{enumerable:!0,value:function(e,t){this.playing||(s.Util.isNumber(t)||(t=this.offsetTime||0),s.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),s.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=o.context.currentTime-t,this.sourceNode.start(s.context.currentTime+e,t)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=s.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/s.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new o.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),t=0;t0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var i=s.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this sound."),this;var i=this.playing;i&&this.pause();var n=0===t?this.fadeNode:this.effectConnectors[t-1];n.disconnect();var o=this.effectConnectors[t];o.disconnect(),e.disconnect(o),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var s;return s=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],n.connect(s),i&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){var e=this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(s.context.currentTime),this.fadeNode.gain.setValueAtTime(e,s.context.currentTime),!this.attack)return void this.fadeNode.gain.setValueAtTime(1,o.context.currentTime);var t=(1-this.fadeNode.gain.value)*this.attack;this.fadeNode.gain.setValueAtTime(this.fadeNode.gain.value,o.context.currentTime),this.fadeNode.gain.linearRampToValueAtTime(1,o.context.currentTime+t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,i=function(){return s.Util.isFunction(t.stop)?t.stop(0):t.disconnect()},n=this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(s.context.currentTime),this.fadeNode.gain.setValueAtTime(n,s.context.currentTime),!this.release)return void i();var a=this.fadeNode.gain.value*this.release;this.fadeNode.gain.setValueAtTime(this.fadeNode.gain.value,o.context.currentTime),this.fadeNode.gain.linearRampToValueAtTime(1e-5,o.context.currentTime+a),window.setTimeout(function(){i()},1e3*a)}}}),o.Group=function(e){e=e||[],this.mergeGainNode=s.context.createGain(),this.masterVolume=s.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(s.masterGainNode);for(var t=0;t-1?void console.warn("The Pizzicato.Sound object was already added to this group"):(e.detached&&console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together."),e.disconnect(s.masterGainNode),e.connect(this.mergeGainNode),void this.sounds.push(e)):void console.error("You can only add Pizzicato.Sound objects")}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);return-1===t?void console.warn("Cannot remove a sound that is not part of this group."):(e.disconnect(this.mergeGainNode),e.connect(s.masterGainNode),void this.sounds.splice(t,1))}},volume:{enumerable:!0,get:function(){return this.masterVolume?this.masterVolume.gain.value:void 0},set:function(e){s.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var i=s.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this group."),this;var i=0===t?this.mergeGainNode:this.effectConnectors(t-1);i.disconnect();var n=this.effectConnectors[t];n.disconnect(),e.disconnect(n),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var o;return o=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],i.connect(o),this}}}),o.Effects={};var f=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});o.Effects.Delay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Delay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),o.Effects.Compressor=function(e){this.options={},e=e||this.options;var t={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};this.inputNode=this.compressorNode=o.context.createDynamicsCompressor(),this.outputNode=o.context.createGain(),this.compressorNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Compressor.prototype=Object.create(f,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){o.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){o.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){o.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){o.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){o.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),o.Effects.LowPassFilter=function(e){t.call(this,e,"lowpass")},o.Effects.HighPassFilter=function(e){t.call(this,e,"highpass")};var p=Object.create(f,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){o.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){o.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});o.Effects.LowPassFilter.prototype=p,o.Effects.HighPassFilter.prototype=p,o.Effects.Distortion=function(e){this.options={},e=e||this.options;var t={gain:.5};this.waveShaperNode=o.context.createWaveShaper(),this.inputNode=this.outputNode=this.waveShaperNode;for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Distortion.prototype=Object.create(f,{gain:{enumerable:!0,get:function(){return this.options.gain},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.gain=e,this.adjustGain())}},adjustGain:{writable:!1,configurable:!1,enumerable:!1,value:function(){for(var e,t=s.Util.isNumber(this.options.gain)?parseInt(100*this.options.gain,10):50,i=44100,n=new Float32Array(i),o=Math.PI/180,a=0;i>a;++a)e=2*a/i-1,n[a]=(3+t)*e*20*o/(Math.PI+t*Math.abs(e));this.waveShaperNode.curve=n}}}),o.Effects.Flanger=function(e){this.options={},e=e||this.options;var t={time:.45,speed:.2,depth:.1,feedback:.1,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.inputFeedbackNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.oscillatorNode=o.context.createOscillator(),this.gainNode=o.context.createGain(),this.feedbackNode=o.context.createGain(),this.oscillatorNode.type="sine",this.inputNode.connect(this.inputFeedbackNode),this.inputNode.connect(this.dryGainNode),this.inputFeedbackNode.connect(this.delayNode),this.inputFeedbackNode.connect(this.wetGainNode),this.delayNode.connect(this.wetGainNode),this.delayNode.connect(this.feedbackNode),this.feedbackNode.connect(this.inputFeedbackNode),this.oscillatorNode.connect(this.gainNode),this.gainNode.connect(this.delayNode.delayTime),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),this.oscillatorNode.start(0);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Flanger.prototype=Object.create(f,{time:{enumberable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.time=e,this.delayNode.delayTime.value=s.Util.normalize(e,.001,.02))}},speed:{enumberable:!0,get:function(){return this.options.speed},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.speed=e,this.oscillatorNode.frequency.value=s.Util.normalize(e,.5,5))}},depth:{enumberable:!0,get:function(){return this.options.depth},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.depth=e,this.gainNode.gain.value=s.Util.normalize(e,5e-4,.005))}},feedback:{enumberable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=e,this.feedbackNode.gain.value=s.Util.normalize(e,0,.8))}},mix:{enumberable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}}}),o.Effects.StereoPanner=function(e){this.options={},e=e||this.options;var t={pan:0};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),o.context.createStereoPanner?(this.pannerNode=o.context.createStereoPanner(),this.inputNode.connect(this.pannerNode),this.pannerNode.connect(this.outputNode)):this.inputNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.StereoPanner.prototype=Object.create(f,{pan:{enumerable:!0,get:function(){return this.options.pan},set:function(e){s.Util.isInRange(e,-1,1)&&(this.options.pan=e,this.pannerNode&&(this.pannerNode.pan.value=e))}}}),o.Effects.Convolver=function(e,t){this.options={},e=e||this.options;var i=this,n=new XMLHttpRequest,a={mix:.5};this.callback=t,this.inputNode=o.context.createGain(),this.convolverNode=o.context.createConvolver(),this.outputNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var r in a)this[r]=e[r],this[r]=void 0===this[r]||null===this[r]?a[r]:this[r];return e.impulse?(n.open("GET",e.impulse,!0),n.responseType="arraybuffer",n.onload=function(e){var t=e.target.response;o.context.decodeAudioData(t,function(e){i.convolverNode.buffer=e,i.callback&&s.Util.isFunction(i.callback)&&i.callback()},function(e){e=e||new Error("Error decoding impulse file"),i.callback&&s.Util.isFunction(i.callback)&&i.callback(e)})},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e.impulse+". "+n.statusText)},void n.send()):void console.error("No impulse file specified.")},o.Effects.Convolver.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}}}),o.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.delayNodeLeft=o.context.createDelay(),this.delayNodeRight=o.context.createDelay(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.channelMerger=o.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.PingPongDelay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),o.Effects.Reverb=function(e){this.options={},e=e||this.options;var t={mix:.5,time:.01,decay:.01,reverse:!1};this.inputNode=o.context.createGain(),this.reverbNode=o.context.createConvolver(),this.outputNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var n in t)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?t[n]:this[n];i.bind(this)()},o.Effects.Reverb.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,1e-4,10)&&(this.options.time=e,i.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){s.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,i.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){s.Util.isBool(e)&&(this.options.reverse=e,i.bind(this)())}}}),o.Effects.Tremolo=function(e){this.options={},e=e||this.options;var t={speed:4,depth:1,mix:.8};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.tremoloGainNode=o.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=o.context.createOscillator(),this.shaperNode=o.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Tremolo.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){s.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),o.Effects.DubDelay=function(e){this.options={},e=e||this.options;var t={feedback:.6,time:.7,mix:.5,cutoff:700};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.bqFilterNode=o.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.DubDelay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){s.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),o.Effects.RingModulator=function(e){this.options={},e=e||this.options;var t={speed:30,distortion:1,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.vIn=o.context.createOscillator(),this.vIn.start(0),this.vInGain=o.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=o.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=o.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new v(o.context),this.vInDiode2=new v(o.context),this.vInInverter3=o.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=o.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new v(o.context),this.vcDiode4=new v(o.context),this.outGain=o.context.createGain(),this.outGain.gain.value=3,this.compressor=o.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]};var v=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};return v.prototype.setDistortion=function(e){return this.h=e,this.setCurve()},v.prototype.setCurve=function(){var e,t,i,n,o,s,a,r;for(t=1024,o=new Float32Array(t),e=s=0,a=o.length;a>=0?a>s:s>a;e=a>=0?++s:--s)i=(e-t/2)/(t/2),i=Math.abs(i),n=i<=this.vb?0:this.vbi;i++)t[i].setDistortion(e)}}}}),o.Effects.Quadrafuzz=function(e){this.options={},e=e||this.options;var t={lowGain:.6,midLowGain:.8,midHighGain:.5,highGain:.6};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.lowpassLeft=s.context.createBiquadFilter(),this.lowpassLeft.type="lowpass",this.lowpassLeft.frequency.value=147,this.lowpassLeft.Q.value=.7071,this.bandpass1Left=s.context.createBiquadFilter(),this.bandpass1Left.type="bandpass",this.bandpass1Left.frequency.value=587,this.bandpass1Left.Q.value=.7071,this.bandpass2Left=s.context.createBiquadFilter(),this.bandpass2Left.type="bandpass",this.bandpass2Left.frequency.value=2490,this.bandpass2Left.Q.value=.7071,this.highpassLeft=s.context.createBiquadFilter(),this.highpassLeft.type="highpass",this.highpassLeft.frequency.value=4980,this.highpassLeft.Q.value=.7071,this.overdrives=[];for(var i=0;4>i;i++)this.overdrives[i]=s.context.createWaveShaper(),this.overdrives[i].curve=n();this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode);var o=[this.lowpassLeft,this.bandpass1Left,this.bandpass2Left,this.highpassLeft];for(i=0;i { + if (flagCtrl) this.ctrlSounds({ CONTROL : "stop" }); + }); + runtime.on("PROJECT_STOP_ALL", () => { + if (flagCtrl) this.ctrlSounds({ CONTROL : "stop" }); + }); + runtime.on("BEFORE_EXECUTE", () => { + const projectVal = runtime.audioEngine.inputNode.gain.value; + Object.keys(soundBank).forEach(key => { + const curVol = Math.min(100, Math.max(0, soundBank[key].vol)) / 100; + soundBank[key].context.volume = curVol * projectVal; + }); + }); + runtime.on("SP_PROJECT_PAUSE", () => { + if (runtime.ioDevices.clock._paused) { + Object.keys(soundBank).forEach(key => { soundBank[key].context.pause() }); + } else { + Object.keys(soundBank).forEach(key => { + const thisSound = soundBank[key].context; + if (thisSound.paused) { + thisSound.play(); + thisSound.sourceNode.playbackRate.value = soundBank[key].pitch; + thisSound.sourceNode.gainSuccessor.gain.value = soundBank[key].gain; + } + }); + } + }); + } + getInfo() { + return { + id: "SPtuneShark3", + name: "Tune Shark V3", + color1: "#666666", + menuIconURI, + blockIconURI, + blocks: [ + { + opcode: "importURL", + blockType: Scratch.BlockType.COMMAND, + text: "import sound from URL [URL] named [NAME]", + blockIconURI: settingsIconURI, + arguments: { + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + }, + }, + { + opcode: "importMenu", + blockType: Scratch.BlockType.COMMAND, + text: "import sound [SOUND] named [NAME]", + blockIconURI: settingsIconURI, + arguments: { + SOUND: { type: Scratch.ArgumentType.SOUND }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + }, + }, + { + opcode: "convertSound", + blockType: Scratch.BlockType.COMMAND, + text: "convert sound [NAME1] from URL to URI and save to [NAME2]", + blockIconURI: settingsIconURI, + arguments: { + NAME1: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME2: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound2" } + }, + }, + { + opcode: "bindSound", + blockType: Scratch.BlockType.COMMAND, + text: "[TYPE] sound [NAME2] and sound [NAME]", + blockIconURI: settingsIconURI, + arguments: { + TYPE: { type: Scratch.ArgumentType.STRING, menu: "bindMenu" }, + NAME2: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound2" } + }, + }, + { blockType: Scratch.BlockType.LABEL, text: "Audio Playback" }, + { + opcode: "startSound", + blockType: Scratch.BlockType.COMMAND, + text: "start sound [NAME]", + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + }, + }, + { + opcode: "startSoundAt", + blockType: Scratch.BlockType.COMMAND, + text: "start sound [NAME] at time [TIME]", + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } + }, + }, + { + opcode: "playAndStop", + blockType: Scratch.BlockType.COMMAND, + text: "start sound [NAME] at time [TIME] and stop at [MAX] seconds", + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + MAX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 } + }, + }, + { + opcode: "stopSound", + blockType: Scratch.BlockType.COMMAND, + text: "stop sound [NAME]", + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + }, + }, + { + opcode: "pauseSound", + blockType: Scratch.BlockType.COMMAND, + text: "[UN_PAUSE] sound [NAME]", + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + UN_PAUSE: { type: Scratch.ArgumentType.STRING, menu: "un_pauseMenu" } + }, + }, + "---", + { + opcode: "ctrlSounds", + blockType: Scratch.BlockType.COMMAND, + text: "[CONTROL] all sounds", + arguments: { + CONTROL: { type: Scratch.ArgumentType.STRING, menu: "playMenu" } + }, + }, + { blockType: Scratch.BlockType.LABEL, text: "Audio Operations" }, + { + opcode: "enableControl", + blockType: Scratch.BlockType.COMMAND, + text: "toggle sound link to [GO] [STOP] [ON_OFF]", + blockIconURI: settingsIconURI, + arguments: { + GO: { type: Scratch.ArgumentType.IMAGE, dataURI: startFlag }, + STOP: { type: Scratch.ArgumentType.IMAGE, dataURI: stopSign }, + ON_OFF: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } + } + }, + { + opcode: "toggleOverlap", + blockType: Scratch.BlockType.COMMAND, + text: "toggle sound [NAME] overlapping [TYPE]", + blockIconURI: settingsIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } + }, + }, + { + opcode: "toggleLoop", + blockType: Scratch.BlockType.COMMAND, + text: "toggle sound [NAME] looping [TYPE]", + blockIconURI: settingsIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } + }, + }, + { + opcode: "loopParams", + blockType: Scratch.BlockType.COMMAND, + text: "sound [NAME] loop start [START] end [END]", + blockIconURI: settingsIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + START: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + END: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 } + }, + }, + "---", + { + opcode: "deleteSound", + blockType: Scratch.BlockType.COMMAND, + text: "delete sound [NAME]", + blockIconURI: settingsIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + }, + }, + { + opcode: "deleteAllSounds", + blockType: Scratch.BlockType.COMMAND, + text: "delete all sounds", + blockIconURI: settingsIconURI + }, + { + opcode: "allSounds", + blockType: Scratch.BlockType.REPORTER, + text: "all sounds", + blockIconURI: settingsIconURI + }, + { + opcode: "allPlaySounds", + blockType: Scratch.BlockType.REPORTER, + text: "all playing sounds", + blockIconURI: settingsIconURI + }, + "---", + { + opcode: "soundCheck", + blockType: Scratch.BlockType.BOOLEAN, + text: "sound [NAME] [CONTROL] ?", + blockIconURI: settingsIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + CONTROL: { type: Scratch.ArgumentType.STRING, menu: "soundBools" } + }, + }, + { + opcode: "soundProperty", + blockType: Scratch.BlockType.REPORTER, + text: "[PROP] of sound [NAME]", + blockIconURI: settingsIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + PROP: { type: Scratch.ArgumentType.STRING, menu: "soundProps" } + }, + }, + { + opcode: "getLoudTime", + blockType: Scratch.BlockType.REPORTER, + text: "loudness of sound [NAME] at time [TIME]", + blockIconURI: settingsIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } + }, + }, + { blockType: Scratch.BlockType.LABEL, text: "Audio Effects" }, + { + opcode: "setVol", + blockType: Scratch.BlockType.COMMAND, + text: "set volume of sound [NAME] to [NUM]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } + }, + }, + { + opcode: "resetEffect", + blockType: Scratch.BlockType.COMMAND, + text: "reset [EFFECT] of sound [NAME]", + blockIconURI: nobIconURI, + arguments: { + EFFECT: { type: Scratch.ArgumentType.STRING, menu: "effectMenu" }, + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + }, + }, + "---", + { + opcode: "setThing", + blockType: Scratch.BlockType.COMMAND, + text: "set [TYPE] of sound [NAME] to [VALUE]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "singleEffects" }, + VALUE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } + }, + }, + { + opcode: "setReverb", + blockType: Scratch.BlockType.COMMAND, + text: "set reverb of sound [NAME] to time [TIME] decay [DECAY] mix [MIX]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, + DECAY: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + }, + }, + { + opcode: "setDelay", + blockType: Scratch.BlockType.COMMAND, + text: "set delay of sound [NAME] to time [TIME] feedback [FEED] mix [MIX]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + FEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, + TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + }, + }, + { + opcode: "setTremolo", + blockType: Scratch.BlockType.COMMAND, + text: "set tremolo of sound [NAME] to speed [SPEED] depth [DEPTH] mix [MIX]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + SPEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 35 }, + DEPTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 80 }, + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } + }, + }, + "---", + { + opcode: "setFuzz", + blockType: Scratch.BlockType.COMMAND, + text: "set fuzz of sound [NAME] to low [LOW] med-low [MED1] med-high [MED2] high [HIGH] mix [MIX]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + LOW: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, + MED1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 80 }, + MED2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, + HIGH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + }, + }, + { + opcode: "setPass", + blockType: Scratch.BlockType.COMMAND, + text: "set [TYPE] of sound [NAME] to frequency [FREQ] peak [PEAK]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "typePass" }, + FREQ: { type: Scratch.ArgumentType.NUMBER, defaultValue: 400 }, + PEAK: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + }, + }, + { + opcode: "setFlanger", + blockType: Scratch.BlockType.COMMAND, + text: "set flanger of sound [NAME] to time [TIME] speed [SPEED] depth [DEPTH] feed [FEED] mix [MIX]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 45 }, + SPEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 20 }, + DEPTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 }, + FEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 }, + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + }, + }, + { + opcode: "setCompress", + blockType: Scratch.BlockType.COMMAND, + text: "set compressor of sound [NAME] to threshold [THRESH] knee [KNEE] attack [ATTACK] release [RELEASE] ratio [RATIO]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + THRESH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, + KNEE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, + ATTACK: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, + RELEASE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, + RATIO: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + }, + } + ], + menus: { + un_pauseMenu: ["pause", "unpause"], + playMenu: ["start", "stop", "pause", "unpause"], + toggleMenu: ["on", "off"], + bindMenu: ["bind", "unBind"], + typePass: ["highpass", "lowpass"], + singleEffects: ["pitch", "pan", "gain", "distortion"], + soundProps: { + acceptReporters: true, + items: [ + "length", "current time", "source", "binds", "volume", "pitch", + "pan", "gain", "distortion", "reverb", "delay", "tremolo", + "fuzz", "highpass", "lowpass", "flanger", "compressor" + ] + }, + soundBools: { + acceptReporters: true, + items: ["exists", "playing", "paused", "looped", "overlaped", "binded"] + }, + effectMenu: { + acceptReporters: true, + items: [ + "all effects", "pitch", "pan", "gain", "distortion", "reverb", "delay", + "tremolo", "fuzz", "highpass", "lowpass", "flanger", "compressor" + ] + } + }, + }; + } + + // Helper Funcs + getSoundIndex(name, target) { + const sounds = target.sounds; + return sounds.indexOf(sounds.filter((sound) => { return sound.name === name })[0]); + } + + calcTime(leng, start, currentT) { + const time = Math.abs(start - currentT); + return Math.min(leng, Math.max(0, time)); + } + + updateEffect(effect, sound, name, args) { + delete args.NAME; + delete args.TYPE; + effect.arguments = args; // Match Original Values, not Converted + if (sound.effects[name] === undefined) { + effect.id = name; + sound.context.addEffect(effect); + sound.effects[name] = effect; + } else { + // Dont Remove the Effect (Causes Glitches in Forever Loops), simply reset each value + const options = effect.options; + const thisEffect = sound.context.effects.find(effect => effect.id === name); + thisEffect.arguments = effect.arguments; + thisEffect.options = options; + switch (name) { + case "PAN": { + thisEffect.pannerNode.pan.value = options.pan; + thisEffect.pan = options.pan; + return; + } + case "DISTORTION": return thisEffect.gain = options.gain; + case "LOWPASS": + case "HIGHPASS": + // These Options Dont Save for Some Reason :/ + const freq = Scratch.Cast.toNumber(args.FREQ); + const peak = Scratch.Cast.toNumber(args.PEAK) / 5; + thisEffect.filterNode.frequency.value = freq; + thisEffect.inputNode.frequency.value = freq; + thisEffect.frequency = freq; + thisEffect.filterNode.Q.value = peak; + thisEffect.inputNode.Q.value = peak; + thisEffect.peak = peak; + return; + case "COMPRESSOR": + // These Options Dont Save for Some Reason :/ + const node = thisEffect.compressorNode; + const values = { + threshold: Math.min(0, Math.max(-100, Scratch.Cast.toNumber(args.THRESH) * -1)), + ratio: Scratch.Cast.toNumber(args.RATIO) / 5, attack: Math.min(0, Math.max(1, Scratch.Cast.toNumber(args.ATTACK) / 100)), + release: Math.min(0, Math.max(1, Scratch.Cast.toNumber(args.RELEASE) / 100)), knee: Scratch.Cast.toNumber(args.KNEE) / 2.5 + }; + Object.keys(values).forEach(key => { node[key].value = values[key] }); + return; + } + Object.keys(options).forEach(key => { thisEffect[key] = options[key] }); + } + } + + play(sound, atTime, con) { + try { + if (sound.playing && con.overlap) { + const clone = sound.clone(); // Clone context to Menu for Control Purposes + const newName = `${con.name}_COPY_${Math.random()}`; + soundBank[newName] = { context: clone, vol: con.vol, overlap: false, gain: con.gain, pitch: con.pitch }; + clone.play(); + clone.sourceNode.playbackRate.value = con.pitch; + con.overlays.push(clone); + clone.on("end", function() { delete soundBank[newName] }); + } else { + sound.play(0, atTime); + sound.sourceNode.playbackRate.value = con.pitch; + sound.sourceNode.gainSuccessor.gain.value = con.gain; + if (Object.keys(con.binds).length > 0) { + Object.keys(con.binds).forEach(key => { + const thisSound = con.binds[key]; + const context = thisSound.context; + context.play(0, atTime); + context.sourceNode.playbackRate.value = thisSound.pitch; + context.sourceNode.gainSuccessor.gain.value = thisSound.gain; + }); + } + } + } catch { + console.warn("Audio has not Loaded Yet, Ignore Next Error"); + sound.stop(); // Reset + } + } + + typeOverlay(sound, type) { + if (type === "stop") { + sound.context.stop(); + for (let i = 0; i < sound.overlays.length; i++) { sound.overlays[i].stop() } + } else if (type === "pause") { + sound.context.pause(); + for (let i = 0; i < sound.overlays.length; i++) { sound.overlays[i].pause() } + } else if (type === "play") { + sound.context.play(); + sound.context.sourceNode.playbackRate.value = sound.pitch; + sound.context.sourceNode.gainSuccessor.gain.value = sound.gain; + for (let i = 0; i < sound.overlays.length; i++) { + sound.overlays[i].play(); + sound.overlays[i].sourceNode.playbackRate.value = sound.pitch; + sound.overlays[i].sourceNode.gainSuccessor.gain.value = sound.gain; + } + } + } + + // Block Funcs + async importURL(args, util) { + return new Promise((resolve) => { + this.deleteSound(args); + if (!args.URL) return resolve(); + const engine = new Pizzicato.Sound(args.URL, () => { + // Expose Buffers + engine.volume = 0; + engine.play(); + engine.stop(); + engine.volume = 1; + soundBank[args.NAME] = { + context: engine, name: args.NAME, src: args.URL, + effects: {}, vol: 100, gain: 1, pitch: 1, + overlap: false, overlays: [], isBind: false, binds: {} + }; + resolve(); + }); + }); + } + + async importMenu(args, util) { + const target = util.target.sprite; + const index = this.getSoundIndex(args.SOUND, target); + if (index < 0) return; + if (runtime.isPackaged) { + alert(`For "Import Scratch Sound" (Tune Shark) to Work, Disable "Remove raw asset data after loading to save RAM" under advanced settings in the packager.`); + return; + } + const sound = target.sounds[index].asset.encodeDataURI(); + await this.importURL({ ...args, URL: sound }, util); + } + + async convertSound(args, util) { + const sound = soundBank[args.NAME1]; + if (sound === undefined) return; + try { + const response = await fetch(sound.src); + const audioBlob = await response.blob(); + const audioDataURL = await new Promise((resolve) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(audioBlob); + }); + await this.importURL({ NAME: args.NAME2, URL: audioDataURL }, util); + } catch (error) { console.error(error.message) } + } + + bindSound(args) { + const sound1 = soundBank[args.NAME]; + const sound2 = soundBank[args.NAME2]; + if (sound1 === undefined || sound2 === undefined) return; + const shouldBind = args.TYPE === "bind"; + sound1.isBind = shouldBind; + sound2.isBind = shouldBind; + if (shouldBind) { + if (sound1.binds[sound2.name]) this.typeOverlay(sound1.binds[sound2.name], "stop"); + if (sound2.binds[sound1.name]) this.typeOverlay(sound2.binds[sound1.name], "stop"); + sound1.binds[sound2.name] = sound2; + sound2.binds[sound1.name] = sound1; + } else { + delete sound1.binds[sound2.name]; + delete sound2.binds[sound1.name]; + } + } + + startSound(args) { + const sound = soundBank[args.NAME]; + if (sound !== undefined) this.play(sound.context, 0, sound); + } + + startSoundAt(args) { + const sound = soundBank[args.NAME]; + const time = Scratch.Cast.toNumber(args.TIME); + if (sound !== undefined) this.play(sound.context, time, sound); + } + + async playAndStop(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + const time = Scratch.Cast.toNumber(args.TIME); + const max = Scratch.Cast.toNumber(args.MAX); + + this.play(sound.context, time, sound); + await new Promise((resolve, reject) => { + setTimeout(() => { + this.typeOverlay(sound, "stop"); + resolve(); + }, (max - time) * 1000); + }); + } + + stopSound(args) { + const sound = soundBank[args.NAME]; + if (sound !== undefined) this.typeOverlay(sound, "stop"); + } + + pauseSound(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + if (args.UN_PAUSE === "pause") this.typeOverlay(sound, "pause"); + else this.typeOverlay(sound, "play"); + } + + ctrlSounds(args) { + if (args.CONTROL === "start") Object.keys(soundBank).forEach(key => { this.play(soundBank[key].context, 0, soundBank[key]) }); + else if (args.CONTROL === "stop") Object.keys(soundBank).forEach(key => { soundBank[key].context.stop() }); + else if (args.CONTROL === "pause") Object.keys(soundBank).forEach(key => { soundBank[key].context.pause() }); + else { + Object.keys(soundBank).forEach(key => { + soundBank[key].context.play(); + soundBank[key].context.sourceNode.playbackRate.value = soundBank[key].pitch; + soundBank[key].context.sourceNode.gainSuccessor.gain.value = soundBank[key].gain; + }); + } + } + + enableControl(args) { flagCtrl = args.ON_OFF === "on" } + + toggleOverlap(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + sound.overlap = args.TYPE === "on"; + } + + toggleLoop(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + sound.context.loop = args.TYPE === "on"; + } + + loopParams(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + sound.context.loop = true; // Auto-turn it on + const srcNode = sound.context.sourceNode; + srcNode.loopStart = Scratch.Cast.toNumber(args.START); + srcNode.loopEnd = Scratch.Cast.toNumber(args.END); + } + + deleteSound(args) { + this.stopSound(args); + delete soundBank[args.NAME]; + } + + deleteAllSounds() { + this.ctrlSounds({ CONTROL: "stop" }); + soundBank = {}; + } + + allSounds() { return JSON.stringify(Object.keys(soundBank)) } + + allPlaySounds() { + const players = []; + Object.entries(soundBank).forEach(([key, innerSrc]) => { + if (innerSrc.context.playing) players.push(key); + }); + return JSON.stringify(players); + } + + soundCheck(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return false; + switch (args.CONTROL) { + case "exists": return true; + case "playing": return sound.context.playing; + case "paused": return sound.context.paused; + case "looped": return sound.context.loop; + case "overlaped": return sound.overlap; + case "binded": return sound.isBind; + default: return false; + } + } + + soundProperty(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return 0; + const src = sound.context.sourceNode; + switch (args.PROP) { + case "length": return src.buffer.duration / sound.pitch; + case "current time": return !sound.context.playing ? 0 : + this.calcTime(src.buffer.duration, sound.context.lastTimePlayed, src.context.currentTime, src) / sound.pitch; + case "source": return sound.src; + case "binds": return JSON.stringify(Object.keys(sound.binds)); + case "volume": return sound.vol; + case "pitch": return Math.round((sound.pitch - 1) * 100); + case "gain": return sound.gain * 100; + case "pan": return sound.effects[args.PROP.toUpperCase()]?.options.pan * 100 || 0; + case "distortion": return sound.effects[args.PROP.toUpperCase()]?.options.gain * 100 || 0; + default: { + const effect = sound.effects[args.PROP.toUpperCase()]; + if (effect === undefined) return ""; + return JSON.stringify(effect.arguments); + } + } + } + + async getLoudTime(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return 0; + const time = Scratch.Cast.toNumber(args.TIME); + const duration = sound.context.sourceNode.buffer.duration; + if (time < 0 || time > duration) return 0; + const audioBuffer = sound.context.sourceNode.buffer; + const sampleRate = audioBuffer.sampleRate; + const channelData = audioBuffer.getChannelData(0); + const sampleIndex = Math.floor(sampleRate * time); + + const windowSize = sampleRate * 0.1; + const startSample = Math.max(0, sampleIndex - windowSize / 2); + const endSample = Math.min(channelData.length, sampleIndex + windowSize / 2); + + let sumOfSquares = 0; + for (let i = startSample; i < endSample; i++) { + sumOfSquares += channelData[i] * channelData[i]; + } + const rms = Math.sqrt(sumOfSquares / (endSample - startSample)); + const dB = 20 * Math.log10(rms); + return Math.min(Math.max((dB + 50) / 50, 0), 1) * 100; + } + + setVol(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + sound.vol = Math.max(0, Scratch.Cast.toNumber(args.NUM)); + } + + resetEffect(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + const oldPitch = sound.context.sourceNode.playbackRate.value; // In-case the Engine Resets it + const oldGain = sound.context.sourceNode.gainSuccessor.gain.value; // In-case the Engine Resets it + if (args.EFFECT === "all effects") { + const effects = sound.effects; + Object.keys(effects).forEach(key => { sound.context.removeEffect(effects[key]) }); + sound.effects = {}; + } + if (args.EFFECT === "all effects" || args.EFFECT === "pitch") { + sound.context.sourceNode.playbackRate.value = 1; + sound.pitch = 1; + } + if (args.EFFECT === "all effects" || args.EFFECT === "gain") { + sound.context.sourceNode.gainSuccessor.gain.value = 1; + sound.gain = 1; + } + const name = args.EFFECT.toUpperCase(); + if (sound.effects[name] !== undefined) { + sound.context.removeEffect(sound.effects[name]); + delete sound.effects[name]; + } + sound.context.sourceNode.playbackRate.value = oldPitch; + sound.context.sourceNode.gainSuccessor.gain.value = oldGain; + } + + setThing(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + const value = Scratch.Cast.toNumber(args.VALUE) / 100; + if (args.TYPE === "pitch") { + sound.pitch = Math.max(0, value + 1) + sound.context.sourceNode.playbackRate.value = sound.pitch; + } else if (args.TYPE === "gain") { + sound.gain = value; + sound.context.sourceNode.gainSuccessor.gain.value = value; + } else if (args.TYPE === "pan") { + const pan = new Pizzicato.Effects.StereoPanner({ pan: Math.max(-1, Math.min(1, value)) }); + this.updateEffect(pan, sound, "PAN", args); + } else { + const distort = new Pizzicato.Effects.Distortion({ gain: value }); + this.updateEffect(distort, sound, "DISTORTION", args); + } + } + + setReverb(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + const reverb = new Pizzicato.Effects.Reverb({ + time: Scratch.Cast.toNumber(args.TIME) / 10, decay: Scratch.Cast.toNumber(args.DECAY) / 10, + mix: Scratch.Cast.toNumber(args.MIX) / 100, + }); + this.updateEffect(reverb, sound, "REVERB", args); + } + + setDelay(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + const delay = new Pizzicato.Effects.Delay({ + time: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.TIME) / 100)), + decay: Scratch.Cast.toNumber(args.FEED) / 100, mix: Scratch.Cast.toNumber(args.MIX) / 100, + }); + this.updateEffect(delay, sound, "DELAY", args); + } + + setFuzz(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + const fuzz = new Pizzicato.Effects.Quadrafuzz({ + lowGain: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.LOW) / 100)), + midLowGain: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.MED1) / 100)), + midHighGain: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.MED2) / 100)), + highGain: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.HIGH) / 100)), + mix: Scratch.Cast.toNumber(args.MIX) / 100 + }); + this.updateEffect(fuzz, sound, "FUZZ", args); + } + + setTremolo(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + const distort = new Pizzicato.Effects.Tremolo({ + speed: Scratch.Cast.toNumber(args.SPEED) / 5, + depth: Scratch.Cast.toNumber(args.DEPTH) / 100, + mix: Scratch.Cast.toNumber(args.MIX) / 100 + }); + this.updateEffect(distort, sound, "TREMOLO", args); + } + + setPass(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + if (args.TYPE === "highpass") { + const highpass = new Pizzicato.Effects.LowPassFilter({ + frequency: Scratch.Cast.toNumber(args.FREQ), peak:Scratch.Cast.toNumber(args.PEAK) / 5 + }); + this.updateEffect(highpass, sound, "HIGHPASS", args); + } else { + const lowpass = new Pizzicato.Effects.LowPassFilter({ + frequency: Scratch.Cast.toNumber(args.FREQ), peak: Scratch.Cast.toNumber(args.PEAK) / 5 + }); + this.updateEffect(lowpass, sound, "LOWPASS", args); + } + } + + setFlanger(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + const flang = new Pizzicato.Effects.Flanger({ + time: Scratch.Cast.toNumber(args.TIME) / 100, speed: Scratch.Cast.toNumber(args.SPEED) / 100, + depth: Scratch.Cast.toNumber(args.DEPTH) / 100, feedback: Scratch.Cast.toNumber(args.FEED) / 100, + mix: Scratch.Cast.toNumber(args.MIX) / 100 + }); + this.updateEffect(flang, sound, "FLANGER", args); + } + + setCompress(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + const compress = new Pizzicato.Effects.Compressor({ + threshold: Math.min(0, Math.max(-100, Scratch.Cast.toNumber(args.THRESH) * -1)), + ratio: Scratch.Cast.toNumber(args.RATIO) / 5, attack: Math.min(0, Math.max(1, Scratch.Cast.toNumber(args.ATTACK) / 100)), + release: Math.min(0, Math.max(1, Scratch.Cast.toNumber(args.RELEASE) / 100)), knee: Scratch.Cast.toNumber(args.KNEE) / 2.5 + }); + this.updateEffect(compress, sound, "COMPRESSOR", args); + } + } + + Scratch.extensions.register(new SPtuneShark3()); +})(Scratch); From 65cfc8d09d806c8e106203c8be2b1bd8ea1c3a01 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sat, 25 May 2024 17:57:24 -0700 Subject: [PATCH 02/40] Update Tune-Shark-V3.js --- extensions/SharkPool/Tune-Shark-V3.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index cfd114d40d..3883997346 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -18,8 +18,6 @@ const blockIconURI = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMjk5LC0xNDAuNTEyOTkpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTIwMC41MTMsMjE5LjQ4NzAxdi03OC45NzQwMWg3OC45NzQwMXY3OC45NzQwMXoiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIvPjxwYXRoIGQ9Ik0yNjEuNzE1MiwxOTQuMzQ4NjZsLTAuMDAxNzcsLTAuMDAyMzZjLTAuMDY4NDQsMy43MDkzNSAtMy4zMzcwNiw3LjI3ODg3IC04LjAxNjM4LDguNDE1ODFjLTUuMzU5LDEuMzAxNTUgLTEwLjUzODA1LC0xLjA4MDMgLTExLjU2NzYxLC01LjMyMDY1Yy0xLjAzMDE1LC00LjI0MDk1IDIuNDc5MTksLTguNzMyNjQgNy44MzgyLC0xMC4wMzQxOWMyLjQ0MDI1LC0wLjU5Mjk1IDQuODQwOTgsLTAuNDIwMDggNi44MzE2NSwwLjM0MjJ2LTIwLjU2MTAzYy0xNC44NDk4LDEuMDAzNiAtMjYuMjk4MjMsNS4wMDYxOCAtMjYuMjk4MjMsNS4wMDYxOHYyNy42NDQ2MmMwLjQ5MDI5LDQuMDEzNzkgLTIuOTE2MzksOC4wNzc3NCAtNy45NjkxOCw5LjMwNTU0Yy01LjM1ODQxLDEuMzAxNTUgLTEwLjUzNzQ2LC0xLjA4MDg5IC0xMS41Njc2MSwtNS4zMjEyNGMtMS4wMzAxNSwtNC4yNDAzNiAyLjQ3OTE5LC04LjczMjA1IDcuODM4MiwtMTAuMDM0MTljMi40ODU2OCwtMC42MDQxNiA0LjkzMzAyLC0wLjQxNDc3IDYuOTQ1NTIsMC4zODQwOWwwLjAxMTgsLTM2Ljk5ODUzYzAsMCAxNi4xODk3LC03LjI5MDY3IDM1Ljk1NTQxLC02LjYzMzQxeiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+"; - const groupIconURI = -"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMywtMTQwLjUxMzAxKSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjAwLjUxMywyMTkuNDg3MDJ2LTc4Ljk3NDAxaDc4Ljk3NDAxdjc4Ljk3NDAxeiIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiLz48cGF0aCBkPSJNMjY1Ljk1NzcxLDE4My4yMzU2NmwtMC4wMDEwOCwtMC4wMDIxNmMtMC42MDQwNywyLjk3OTU0IC0zLjc2Njc0LDUuMzcyODIgLTcuNzA2NDMsNS41OTY3MmMtNC41MTE4NiwwLjI1NiAtOC4zMzM2MSwtMi40MzAxMiAtOC41MzU5MywtNi4wMDAxMmMtMC4yMDI3MSwtMy41NzA1NyAzLjI5MDQ0LC02LjY3MTQ4IDcuODAyMzEsLTYuOTI3NDhjMi4wNTQ1NCwtMC4xMTY4IDMuOTYzOSwwLjM3Nzc5IDUuNDU1NTUsMS4yODY3NWwzLjA0MjYyLC0xNi41NzE4MWMtMTIuMTE3MTgsLTEuMzg4NTkgLTIxLjkzNjcxLDAuMTQzMjggLTIxLjkzNjcxLDAuMTQzMjhsLTQuMDkwODUsMjIuMjgxMDZjLTAuMTk4OCwzLjMwNzU5IC0zLjU0NTksNi4wNzg5NCAtNy44MDAwNSw2LjMyMDgxYy00LjUxMTM4LDAuMjU2MDkgLTguMzMzMDUsLTIuNDMwNTEgLTguNTM1ODQsLTYuMDAwNmMtMC4yMDI3OSwtMy41NzAwOSAzLjI5MDM1LC02LjY3MSA3LjgwMjMxLC02LjkyNzQ4YzIuMDkyODIsLTAuMTE5MTEgNC4wMzczLDAuMzk1NjkgNS41NDExMiwxLjMzNzM3bDUuNDg0NTUsLTI5LjgxODM5YzAsMCAxNC4xMjc0NywtMy40ODAzOSAyOS45NjEwMSwtMC4wMjU3MnoiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2luZGV4JnF1b3Q7Om51bGx9IiBmaWxsPSIjZmZmZmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjxwYXRoIGQ9Ik0yMTYuNzI2NDQsMjA2LjAzNzAxYy0zLjQxNjc5LC0xNC45NjcwOSAtOS4xNTg2NiwtNDMuMDc4MzQgLTkuMTU4NjYsLTQzLjA3ODM0Yy0wLjIyNDE5LC0xLjEyNjkgNC4yOTI1MiwtMS44MTU2OCA0LjU3MzU1LC0wLjY1Njk4YzIuNTIzMjYsMTEuNjUzNzggNy4wNjQwOSwzOC4wOTc0NiA4LjY2OTU2LDM3LjE4Njk3YzMuMjM3MTcsLTIuMDkwMDUgNy40NTg4MSwtMi41NjYzMyAxMS4xMjY4MiwtMS4zOTgyNWMzLjY0NzMxLDAuNzcwNzcgNy4wOTcxMywzLjY4NDY0IDcuNjk1NDcsNy40NzUxMWMwLjYyNTM4LDMuMjQ1ODMgLTIuMDA1MjksNi4wODk0OSAtNC45MzI0OSw3LjAzMzU5Yy0zLjA1OTk0LDEuNDI4NjMgLTYuNTc4MjMsMS4yMDA4MyAtOS43MDU3OSwwLjEyMjY0Yy0zLjQ2MzU0LC0wLjk4ODk5IC03LjMzNzY1LC0yLjg2MTkxIC04LjI2ODQ4LC02LjY4NDczeiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvZz48L2c+PC9zdmc+"; const settingsIconURI = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMjk5LC0xNDAuNTEyOTgpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjY1LjA0NTY5LDE4My4xMjI3MWwyLjA0MiwwLjg1NTIxYzIuNTU3MTgsMS4wNzA5NyAzLjc2MTk4LDQuMDEyMTggMi42OTEwMSw2LjU2OTM2bC0xLjM3MTM0LDMuMjc0MzhjLTEuMDcwOTcsMi41NTcxOCAtNC4wMTIxOCwzLjc2MiAtNi41NjkzNiwyLjY5MTAzbC0yLjA0MiwtMC44NTUyMWMtMS4yNjc5NiwxLjYwNDkgLTIuNzEzMzcsMy4wMzAxMiAtNC4yOTQ4Nyw0LjI1ODcxbDAuODM5OTUsMi4wNTA1MmMxLjA1MDkxLDIuNTY1NDkgLTAuMTc2OSw1LjQ5NzE2IC0yLjc0MjQsNi41NDgwN2wtMy4yODUwMywxLjM0NTY1Yy0yLjU2NTQ5LDEuMDUwOTEgLTUuNDk3MTYsLTAuMTc2OSAtNi41NDgwNywtMi43NDIzOWwtMC44Mzk5NiwtMi4wNTA1MmMtMS45ODg5MiwwLjIzMzk4IC00LjAxODgxLDAuMjMyMzYgLTYuMDQ4MzEsLTAuMDIxODFsLTAuODU1MiwyLjA0MTk5Yy0xLjA3MDk3LDIuNTU3MTggLTQuMDEyMTgsMy43NjE5OCAtNi41NjkzNiwyLjY5MTAxbC0zLjI3NDM4LC0xLjM3MTM0Yy0yLjU1NzE4LC0xLjA3MDk3IC0zLjc2MiwtNC4wMTIxNyAtMi42OTEwMywtNi41NjkzNWwwLjg1NTIxLC0yLjA0MTk5Yy0xLjYwNDksLTEuMjY3OTYgLTMuMDMwMTMsLTIuNzEzMzcgLTQuMjU4NzEsLTQuMjk0ODdsLTIuMDUwNTIsMC44Mzk5NWMtMi41NjU0OSwxLjA1MDkxIC01LjQ5NzE2LC0wLjE3NjkgLTYuNTQ4MDcsLTIuNzQyMzlsLTEuMzQ1NjYsLTMuMjg1MDNjLTEuMDUwOTEsLTIuNTY1NDkgMC4xNzY5LC01LjQ5NzE3IDIuNzQyNCwtNi41NDgwOGwyLjA1MDUyLC0wLjgzOTk2Yy0wLjIzMzk3LC0xLjk4ODkyIC0wLjIzMjM2LC00LjAxODggMC4wMjE4MSwtNi4wNDgzbC0yLjA0MiwtMC44NTUyMWMtMi41NTcxOCwtMS4wNzA5NyAtMy43NjE5OCwtNC4wMTIxOCAtMi42OTEwMSwtNi41NjkzNmwxLjM3MTM1LC0zLjI3NDM4YzEuMDcwOTcsLTIuNTU3MTggNC4wMTIxNywtMy43NjIgNi41NjkzNSwtMi42OTEwM2wyLjA0MiwwLjg1NTIxYzEuMjY3OTYsLTEuNjA0OSAyLjcxMzM4LC0zLjAzMDEyIDQuMjk0ODgsLTQuMjU4NzFsLTAuODM5OTYsLTIuMDUwNTJjLTEuMDUwOTEsLTIuNTY1NDkgMC4xNzY5LC01LjQ5NzE2IDIuNzQyNCwtNi41NDgwN2wzLjI4NTAzLC0xLjM0NTY1YzIuNTY1NDksLTEuMDUwOTEgNS40OTcxNiwwLjE3NjkgNi41NDgwNywyLjc0MjM5bDAuODM5OTYsMi4wNTA1MmMxLjk4ODkyLC0wLjIzMzk3IDQuMDE4ODEsLTAuMjMyMzYgNi4wNDgzMSwwLjAyMTgxbDAuODU1MjEsLTIuMDQyYzEuMDcwOTcsLTIuNTU3MTggNC4wMTIxOCwtMy43NjE5OCA2LjU2OTM2LC0yLjY5MTAxbDMuMjc0MzgsMS4zNzEzNGMyLjU1NzE4LDEuMDcwOTcgMy43NjIsNC4wMTIxNyAyLjY5MTAzLDYuNTY5MzVsLTAuODU1MjEsMi4wNDJjMS42MDQ5LDEuMjY3OTYgMy4wMzAxMywyLjcxMzM3IDQuMjU4NzEsNC4yOTQ4N2wyLjA1MDUyLC0wLjgzOTk1YzIuNTY1NDksLTEuMDUwOTEgNS40OTcxNywwLjE3NjkgNi41NDgwOCwyLjc0MjM5bDEuMzQ1NjUsMy4yODUwM2MxLjA1MDkxLDIuNTY1NDkgLTAuMTc2OSw1LjQ5NzE2IC0yLjc0MjQsNi41NDgwN2wtMi4wNTA1MiwwLjgzOTk2YzAuMjMzOTcsMS45ODg5MiAwLjIzMjM2LDQuMDE4OCAtMC4wMjE4MSw2LjA0ODN6TTIyNy41NDgyMiwxNzQuNzg1MDdjLTIuODgwMTMsNi44NzY5NSAwLjM1OTkyLDE0Ljc4NjYyIDcuMjM2ODYsMTcuNjY2NzVjNi44NzY5NSwyLjg4MDEzIDE0Ljc4NjYyLC0wLjM1OTkyIDE3LjY2Njc1LC03LjIzNjg2YzIuODgwMTMsLTYuODc2OTUgLTAuMzU5OTIsLTE0Ljc4NjYyIC03LjIzNjg2LC0xNy42NjY3NWMtNi44NzY5NSwtMi44ODAxMyAtMTQuNzg2NjIsMC4zNTk5MiAtMTcuNjY2NzUsNy4yMzY4NnoiIGZpbGw9IiNmZmZmZmYiLz48cGF0aCBkPSJNMjAwLjUxMywyMTkuNDg3di03OC45NzQwMWg3OC45NzQwMXY3OC45NzQwMXoiIGZpbGw9Im5vbmUiLz48L2c+PC9nPjwvc3ZnPg=="; const nobIconURI = @@ -473,9 +471,9 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ thisEffect.pan = options.pan; return; } - case "DISTORTION": return thisEffect.gain = options.gain; + case "DISTORTION": { return thisEffect.gain = options.gain } case "LOWPASS": - case "HIGHPASS": + case "HIGHPASS": { // These Options Dont Save for Some Reason :/ const freq = Scratch.Cast.toNumber(args.FREQ); const peak = Scratch.Cast.toNumber(args.PEAK) / 5; @@ -486,7 +484,8 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ thisEffect.inputNode.Q.value = peak; thisEffect.peak = peak; return; - case "COMPRESSOR": + } + case "COMPRESSOR": { // These Options Dont Save for Some Reason :/ const node = thisEffect.compressorNode; const values = { @@ -496,6 +495,7 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ }; Object.keys(values).forEach(key => { node[key].value = values[key] }); return; + } } Object.keys(options).forEach(key => { thisEffect[key] = options[key] }); } @@ -551,7 +551,7 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ } // Block Funcs - async importURL(args, util) { + importURL(args, util) { return new Promise((resolve) => { this.deleteSound(args); if (!args.URL) return resolve(); @@ -587,7 +587,7 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ const sound = soundBank[args.NAME1]; if (sound === undefined) return; try { - const response = await fetch(sound.src); + const response = await Scratch.fetch(sound.src); const audioBlob = await response.blob(); const audioDataURL = await new Promise((resolve) => { const reader = new FileReader(); @@ -747,7 +747,7 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ } } - async getLoudTime(args) { + getLoudTime(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return 0; const time = Scratch.Cast.toNumber(args.TIME); From 07e736ca0fa2c8fbf7b9beead877d6fcd8c81fda Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sat, 25 May 2024 18:02:31 -0700 Subject: [PATCH 03/40] Update Tune-Shark-V3.js --- extensions/SharkPool/Tune-Shark-V3.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 3883997346..0771bc7634 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -34,6 +34,7 @@ `!function(e){"use strict";function t(e,t){this.options={},e=e||this.options;var i={frequency:350,peak:1};this.inputNode=this.filterNode=s.context.createBiquadFilter(),this.filterNode.type=t,this.outputNode=o.context.createGain(),this.filterNode.connect(this.outputNode);for(var n in i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]}function i(){var e,t,i=s.context.sampleRate*this.time,n=o.context.createBuffer(2,i,s.context.sampleRate),a=n.getChannelData(0),r=n.getChannelData(1);for(t=0;i>t;t++)e=this.reverse?i-t:t,a[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay),r[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay);this.reverbNode.buffer=n}function n(e){for(var t=s.context.sampleRate,i=new Float32Array(t),n=Math.PI/180,o=0;t>o;o++){var a=2*o/t-1;i[o]=(3+e)*a*20*n/(Math.PI+e*Math.abs(a))}return i}var o={},s=o,a="object"==typeof module&&module.exports,r="function"==typeof define&&define.amd;a?module.exports=o:r?define([],o):e.Pizzicato=e.Pz=o;var c=e.AudioContext||e.webkitAudioContext;if(!c)return void console.error("No AudioContext found in this environment. Please ensure your window or global object contains a working AudioContext constructor function.");o.context=new c;var h=o.context.createGain();h.connect(o.context.destination),o.Util={isString:function(e){return"[object String]"===toString.call(e)},isObject:function(e){return"[object Object]"===toString.call(e)},isFunction:function(e){return"[object Function]"===toString.call(e)},isNumber:function(e){return"[object Number]"===toString.call(e)&&e===+e},isArray:function(e){return"[object Array]"===toString.call(e)},isInRange:function(e,t,i){return s.Util.isNumber(e)&&s.Util.isNumber(t)&&s.Util.isNumber(i)?e>=t&&i>=e:!1},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof s.Sound},isEffect:function(e){for(var t in o.Effects)if(e instanceof o.Effects[t])return!0;return!1},normalize:function(e,t,i){return s.Util.isNumber(e)&&s.Util.isNumber(t)&&s.Util.isNumber(i)?(i-t)*e/1+t:void 0},getDryLevel:function(e){return!s.Util.isNumber(e)||e>1||0>e?0:.5>=e?1:1-2*(e-.5)},getWetLevel:function(e){return!s.Util.isNumber(e)||e>1||0>e?0:e>=.5?1:1-2*(.5-e)}};var u=o.context.createGain(),d=Object.getPrototypeOf(Object.getPrototypeOf(u)),l=d.connect;d.connect=function(e){var t=s.Util.isEffect(e)?e.inputNode:e;return l.call(this,t),e},Object.defineProperty(o,"volume",{enumerable:!0,get:function(){return h.gain.value},set:function(e){s.Util.isInRange(e,0,1)&&h&&(h.gain.value=e)}}),Object.defineProperty(o,"masterGainNode",{enumerable:!1,get:function(){return h},set:function(e){console.error("Can't set the master gain node")}}),o.Events={on:function(e,t,i){if(e&&t){this._events=this._events||{};var n=this._events[e]||(this._events[e]=[]);n.push({callback:t,context:i||this,handler:this})}},trigger:function(e){if(e){var t,i,n,o;if(this._events=this._events||{},t=this._events[e]||(this._events[e]=[])){for(i=Math.max(0,arguments.length-1),n=[],o=0;i>o;o++)n[o]=arguments[o+1];for(o=0;o1?(e.shift(),void a(e,t)):(i=i||new Error("Error decoding audio file "+e[0]),void(d.isFunction(t)&&t(i)))}.bind(u))},i.onreadystatechange=function(t){4===i.readyState&&200!==i.status&&console.error("Error while fetching "+e[0]+". "+i.statusText)},i.send()}function r(e,t){return navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,navigator.getUserMedia?void navigator.getUserMedia({audio:!0},function(e){u.getRawSourceNode=function(){return o.context.createMediaStreamSource(e)},d.isFunction(t)&&t()}.bind(u),function(e){d.isFunction(t)&&t(e)}):void console.error("Your browser does not support getUserMedia")}function c(e,t){var i=d.isFunction(e)?e:e.audioFunction,n=d.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!n)try{o.context.createScriptProcessor()}catch(s){n=2048}this.getRawSourceNode=function(){var e=o.context.createScriptProcessor(n,1,1);return e.onaudioprocess=i,e}}function h(e,t){this.getRawSourceNode=e.sound.getRawSourceNode,e.sound.sourceNode&&s.Util.isOscillator(e.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=e.sound.frequency)}var u=this,d=o.Util,l=i(e),f=d.isObject(e)&&d.isObject(e.options),p=.04,v=.04;if(l)throw console.error(l),new Error("Error initializing Pizzicato Sound: "+l);this.detached=f&&e.options.detached,this.masterVolume=o.context.createGain(),this.fadeNode=o.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(o.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=f&&e.options.loop,this.attack=f&&d.isNumber(e.options.attack)?e.options.attack:p,this.volume=f&&d.isNumber(e.options.volume)?e.options.volume:1,f&&d.isNumber(e.options.release)?this.release=e.options.release:f&&d.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=v,e?d.isString(e)?a.bind(this)(e,t):d.isFunction(e)?c.bind(this)(e,t):"file"===e.source?a.bind(this)(e.options.path,t):"wave"===e.source?n.bind(this)(e.options,t):"input"===e.source?r.bind(this)(e,t):"script"===e.source?c.bind(this)(e.options,t):"sound"===e.source&&h.bind(this)(e.options,t):n.bind(this)({},t)},o.Sound.prototype=Object.create(o.Events,{play:{enumerable:!0,value:function(e,t){this.playing||(s.Util.isNumber(t)||(t=this.offsetTime||0),s.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),s.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=o.context.currentTime-t,this.sourceNode.start(s.context.currentTime+e,t)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=s.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/s.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new o.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),t=0;t0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var i=s.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this sound."),this;var i=this.playing;i&&this.pause();var n=0===t?this.fadeNode:this.effectConnectors[t-1];n.disconnect();var o=this.effectConnectors[t];o.disconnect(),e.disconnect(o),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var s;return s=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],n.connect(s),i&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){var e=this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(s.context.currentTime),this.fadeNode.gain.setValueAtTime(e,s.context.currentTime),!this.attack)return void this.fadeNode.gain.setValueAtTime(1,o.context.currentTime);var t=(1-this.fadeNode.gain.value)*this.attack;this.fadeNode.gain.setValueAtTime(this.fadeNode.gain.value,o.context.currentTime),this.fadeNode.gain.linearRampToValueAtTime(1,o.context.currentTime+t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,i=function(){return s.Util.isFunction(t.stop)?t.stop(0):t.disconnect()},n=this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(s.context.currentTime),this.fadeNode.gain.setValueAtTime(n,s.context.currentTime),!this.release)return void i();var a=this.fadeNode.gain.value*this.release;this.fadeNode.gain.setValueAtTime(this.fadeNode.gain.value,o.context.currentTime),this.fadeNode.gain.linearRampToValueAtTime(1e-5,o.context.currentTime+a),window.setTimeout(function(){i()},1e3*a)}}}),o.Group=function(e){e=e||[],this.mergeGainNode=s.context.createGain(),this.masterVolume=s.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(s.masterGainNode);for(var t=0;t-1?void console.warn("The Pizzicato.Sound object was already added to this group"):(e.detached&&console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together."),e.disconnect(s.masterGainNode),e.connect(this.mergeGainNode),void this.sounds.push(e)):void console.error("You can only add Pizzicato.Sound objects")}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);return-1===t?void console.warn("Cannot remove a sound that is not part of this group."):(e.disconnect(this.mergeGainNode),e.connect(s.masterGainNode),void this.sounds.splice(t,1))}},volume:{enumerable:!0,get:function(){return this.masterVolume?this.masterVolume.gain.value:void 0},set:function(e){s.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var i=s.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this group."),this;var i=0===t?this.mergeGainNode:this.effectConnectors(t-1);i.disconnect();var n=this.effectConnectors[t];n.disconnect(),e.disconnect(n),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var o;return o=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],i.connect(o),this}}}),o.Effects={};var f=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});o.Effects.Delay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Delay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),o.Effects.Compressor=function(e){this.options={},e=e||this.options;var t={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};this.inputNode=this.compressorNode=o.context.createDynamicsCompressor(),this.outputNode=o.context.createGain(),this.compressorNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Compressor.prototype=Object.create(f,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){o.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){o.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){o.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){o.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){o.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),o.Effects.LowPassFilter=function(e){t.call(this,e,"lowpass")},o.Effects.HighPassFilter=function(e){t.call(this,e,"highpass")};var p=Object.create(f,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){o.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){o.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});o.Effects.LowPassFilter.prototype=p,o.Effects.HighPassFilter.prototype=p,o.Effects.Distortion=function(e){this.options={},e=e||this.options;var t={gain:.5};this.waveShaperNode=o.context.createWaveShaper(),this.inputNode=this.outputNode=this.waveShaperNode;for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Distortion.prototype=Object.create(f,{gain:{enumerable:!0,get:function(){return this.options.gain},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.gain=e,this.adjustGain())}},adjustGain:{writable:!1,configurable:!1,enumerable:!1,value:function(){for(var e,t=s.Util.isNumber(this.options.gain)?parseInt(100*this.options.gain,10):50,i=44100,n=new Float32Array(i),o=Math.PI/180,a=0;i>a;++a)e=2*a/i-1,n[a]=(3+t)*e*20*o/(Math.PI+t*Math.abs(e));this.waveShaperNode.curve=n}}}),o.Effects.Flanger=function(e){this.options={},e=e||this.options;var t={time:.45,speed:.2,depth:.1,feedback:.1,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.inputFeedbackNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.oscillatorNode=o.context.createOscillator(),this.gainNode=o.context.createGain(),this.feedbackNode=o.context.createGain(),this.oscillatorNode.type="sine",this.inputNode.connect(this.inputFeedbackNode),this.inputNode.connect(this.dryGainNode),this.inputFeedbackNode.connect(this.delayNode),this.inputFeedbackNode.connect(this.wetGainNode),this.delayNode.connect(this.wetGainNode),this.delayNode.connect(this.feedbackNode),this.feedbackNode.connect(this.inputFeedbackNode),this.oscillatorNode.connect(this.gainNode),this.gainNode.connect(this.delayNode.delayTime),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),this.oscillatorNode.start(0);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Flanger.prototype=Object.create(f,{time:{enumberable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.time=e,this.delayNode.delayTime.value=s.Util.normalize(e,.001,.02))}},speed:{enumberable:!0,get:function(){return this.options.speed},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.speed=e,this.oscillatorNode.frequency.value=s.Util.normalize(e,.5,5))}},depth:{enumberable:!0,get:function(){return this.options.depth},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.depth=e,this.gainNode.gain.value=s.Util.normalize(e,5e-4,.005))}},feedback:{enumberable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=e,this.feedbackNode.gain.value=s.Util.normalize(e,0,.8))}},mix:{enumberable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}}}),o.Effects.StereoPanner=function(e){this.options={},e=e||this.options;var t={pan:0};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),o.context.createStereoPanner?(this.pannerNode=o.context.createStereoPanner(),this.inputNode.connect(this.pannerNode),this.pannerNode.connect(this.outputNode)):this.inputNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.StereoPanner.prototype=Object.create(f,{pan:{enumerable:!0,get:function(){return this.options.pan},set:function(e){s.Util.isInRange(e,-1,1)&&(this.options.pan=e,this.pannerNode&&(this.pannerNode.pan.value=e))}}}),o.Effects.Convolver=function(e,t){this.options={},e=e||this.options;var i=this,n=new XMLHttpRequest,a={mix:.5};this.callback=t,this.inputNode=o.context.createGain(),this.convolverNode=o.context.createConvolver(),this.outputNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var r in a)this[r]=e[r],this[r]=void 0===this[r]||null===this[r]?a[r]:this[r];return e.impulse?(n.open("GET",e.impulse,!0),n.responseType="arraybuffer",n.onload=function(e){var t=e.target.response;o.context.decodeAudioData(t,function(e){i.convolverNode.buffer=e,i.callback&&s.Util.isFunction(i.callback)&&i.callback()},function(e){e=e||new Error("Error decoding impulse file"),i.callback&&s.Util.isFunction(i.callback)&&i.callback(e)})},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e.impulse+". "+n.statusText)},void n.send()):void console.error("No impulse file specified.")},o.Effects.Convolver.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}}}),o.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.delayNodeLeft=o.context.createDelay(),this.delayNodeRight=o.context.createDelay(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.channelMerger=o.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.PingPongDelay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),o.Effects.Reverb=function(e){this.options={},e=e||this.options;var t={mix:.5,time:.01,decay:.01,reverse:!1};this.inputNode=o.context.createGain(),this.reverbNode=o.context.createConvolver(),this.outputNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var n in t)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?t[n]:this[n];i.bind(this)()},o.Effects.Reverb.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,1e-4,10)&&(this.options.time=e,i.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){s.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,i.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){s.Util.isBool(e)&&(this.options.reverse=e,i.bind(this)())}}}),o.Effects.Tremolo=function(e){this.options={},e=e||this.options;var t={speed:4,depth:1,mix:.8};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.tremoloGainNode=o.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=o.context.createOscillator(),this.shaperNode=o.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Tremolo.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){s.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),o.Effects.DubDelay=function(e){this.options={},e=e||this.options;var t={feedback:.6,time:.7,mix:.5,cutoff:700};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.bqFilterNode=o.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.DubDelay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){s.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),o.Effects.RingModulator=function(e){this.options={},e=e||this.options;var t={speed:30,distortion:1,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.vIn=o.context.createOscillator(),this.vIn.start(0),this.vInGain=o.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=o.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=o.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new v(o.context),this.vInDiode2=new v(o.context),this.vInInverter3=o.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=o.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new v(o.context),this.vcDiode4=new v(o.context),this.outGain=o.context.createGain(),this.outGain.gain.value=3,this.compressor=o.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]};var v=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};return v.prototype.setDistortion=function(e){return this.h=e,this.setCurve()},v.prototype.setCurve=function(){var e,t,i,n,o,s,a,r;for(t=1024,o=new Float32Array(t),e=s=0,a=o.length;a>=0?a>s:s>a;e=a>=0?++s:--s)i=(e-t/2)/(t/2),i=Math.abs(i),n=i<=this.vb?0:this.vbi;i++)t[i].setDistortion(e)}}}}),o.Effects.Quadrafuzz=function(e){this.options={},e=e||this.options;var t={lowGain:.6,midLowGain:.8,midHighGain:.5,highGain:.6};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.lowpassLeft=s.context.createBiquadFilter(),this.lowpassLeft.type="lowpass",this.lowpassLeft.frequency.value=147,this.lowpassLeft.Q.value=.7071,this.bandpass1Left=s.context.createBiquadFilter(),this.bandpass1Left.type="bandpass",this.bandpass1Left.frequency.value=587,this.bandpass1Left.Q.value=.7071,this.bandpass2Left=s.context.createBiquadFilter(),this.bandpass2Left.type="bandpass",this.bandpass2Left.frequency.value=2490,this.bandpass2Left.Q.value=.7071,this.highpassLeft=s.context.createBiquadFilter(),this.highpassLeft.type="highpass",this.highpassLeft.frequency.value=4980,this.highpassLeft.Q.value=.7071,this.overdrives=[];for(var i=0;4>i;i++)this.overdrives[i]=s.context.createWaveShaper(),this.overdrives[i].curve=n();this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode);var o=[this.lowpassLeft,this.bandpass1Left,this.bandpass2Left,this.highpassLeft];for(i=0;i Date: Sat, 25 May 2024 18:11:39 -0700 Subject: [PATCH 04/40] Tune-Shark-V3 (License & Default URL Change) --- extensions/SharkPool/Tune-Shark-V3.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 0771bc7634..9ab98712d4 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -2,9 +2,9 @@ // ID: SPtuneShark3 // Description: Advanced Audio Engine, giving Complex Sound Control // By: SharkPool +// License: MIT AND LGPL-3.0 // Version V.3.0.01 -// Credit to HOME for the song "Resonance" being used as the default audio link (function (Scratch) { "use strict"; @@ -95,7 +95,7 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ text: "import sound from URL [URL] named [NAME]", blockIconURI: settingsIconURI, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, From d08e795d193c7a5cb4574d95a9d8f7de593018a3 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sat, 25 May 2024 18:21:35 -0700 Subject: [PATCH 05/40] Create Tune-Shark-V3.svg --- images/SharkPool/Tune-Shark-V3.svg | 117 +++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 images/SharkPool/Tune-Shark-V3.svg diff --git a/images/SharkPool/Tune-Shark-V3.svg b/images/SharkPool/Tune-Shark-V3.svg new file mode 100644 index 0000000000..ead191f404 --- /dev/null +++ b/images/SharkPool/Tune-Shark-V3.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ec2210e43a9f710f4ae34caa8fdafa90d23c8abe Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 26 May 2024 11:17:52 -0700 Subject: [PATCH 06/40] Tune-Shark-V3 (Import Sound Fixes) --- extensions/SharkPool/Tune-Shark-V3.js | 36 ++++++++++++++++----------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 9ab98712d4..68cd528775 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.0.01 +// Version V.3.0.02 (function (Scratch) { "use strict"; @@ -12,6 +12,7 @@ const vm = Scratch.vm; const runtime = vm.runtime; + const scratchAudio = runtime.audioEngine; const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMDIuMTg1MTgiIGhlaWdodD0iMTAyLjE4NTE4IiB2aWV3Qm94PSIwLDAsMTAyLjE4NTE4LDEwMi4xODUxOCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE4OC45MDc0MSwtMTI4LjkwNzQxKSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTg4LjkwNzQxLDE4MGMwLC0yOC4yMTc2NiAyMi44NzQ5MywtNTEuMDkyNTkgNTEuMDkyNTksLTUxLjA5MjU5YzI4LjIxNzY2LDAgNTEuMDkyNTksMjIuODc0OTMgNTEuMDkyNTksNTEuMDkyNTljMCwyOC4yMTc2NiAtMjIuODc0OTMsNTEuMDkyNTkgLTUxLjA5MjU5LDUxLjA5MjU5Yy0yOC4yMTc2NiwwIC01MS4wOTI1OSwtMjIuODc0OTMgLTUxLjA5MjU5LC01MS4wOTI1OXoiIGZpbGw9IiM0MDQwNDAiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTE5My43Njc0NCwxODBjMCwtMjUuNTMzNTQgMjAuNjk5MDIsLTQ2LjIzMjU2IDQ2LjIzMjU2LC00Ni4yMzI1NmMyNS41MzM1NCwwIDQ2LjIzMjU2LDIwLjY5OTAyIDQ2LjIzMjU2LDQ2LjIzMjU2YzAsMjUuNTMzNTQgLTIwLjY5OTAyLDQ2LjIzMjU2IC00Ni4yMzI1Niw0Ni4yMzI1NmMtMjUuNTMzNTQsMCAtNDYuMjMyNTYsLTIwLjY5OTAyIC00Ni4yMzI1NiwtNDYuMjMyNTZ6IiBmaWxsPSIjNjY2NjY2IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjxwYXRoIGQ9Ik0yNjEuNzQzNjIsMjE1LjU0OTY4bC0wLjAwMDQ2LC0wLjAwMjJjLTEuMjUyOTUsMi41MzUzOSAtNC42NjcsMy45MzQxMiAtOC4yNjMsMy4xOTY5OWMtNC4xMTgxNiwtMC44NDQ1NSAtNi45MTQ5NCwtNC4xNzAzNiAtNi4yNDY0NiwtNy40Mjg3OGMwLjY2ODI1LC0zLjI1OTA0IDQuNTQ4MDQsLTUuMjE1MzYgOC42NjYyMiwtNC4zNzA4MWMxLjg3NTMyLDAuMzg0MzggMy40NzQ0NSwxLjI4Mzk1IDQuNTk5MjYsMi40NTY2M2w2LjY4MzUzLC0xNC4xNzcwNGMtMTAuNTY1MzEsLTQuMTM1MDUgLTE5Ljc2MDIsLTUuMDk2NjQgLTE5Ljc2MDIsLTUuMDk2NjRsLTguOTg2MTEsMTkuMDYxMjRjLTAuOTY2NjUsMi45MjY5MiAtNC42MzY2MSw0LjYyMTY4IC04LjUxOTY3LDMuODI1ODJjLTQuMTE3NzUsLTAuODQ0MzYgLTYuOTE0MzMsLTQuMTcwNTcgLTYuMjQ2MjcsLTcuNDI5MTljMC42NjgwNiwtMy4yNTg2MyA0LjU0Nzg1LC01LjIxNDk2IDguNjY2MjIsLTQuMzcwODFjMS45MTAyOSwwLjM5MTQyIDMuNTM2MTksMS4zMTc1MyA0LjY2NDE1LDIuNTIyNTNsMTIuMDM0OCwtMjUuNTA3MDJjMCwwIDEzLjUzMjg1LDAuMjM1NiAyNi45NDc4Niw3LjExMzc5eiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTIxNy4yMTYyNSwxNTkuNzM4MzJsNC4wNDI1NiwyMy42ODM0NyIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjxwYXRoIGQ9Ik0yMjIuOTM5OTYsMTg0LjEyNTU1YzEuMzgwOTIsMi42MDY0NiAtMC43NzE4NCw2LjA0MDQ2IC00LjgwODMzLDcuNjcwMDVjLTQuMDM2NDksMS42Mjk1OSAtOC40MjgxNywwLjgzNzY2IC05LjgwOTEsLTEuNzY4OGMtMS4zODA5MiwtMi42MDY0NiAwLjc3MTg0LC02LjA0MDQ2IDQuODA4MzMsLTcuNjcwMDVjNC4wMzY0OSwtMS42Mjk1OSA4LjQyODE3LC0wLjgzNzY2IDkuODA5MSwxLjc2ODh6IiBmaWxsPSIjZmZmZmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTIxNC45ODk2MywxNTcuMDU4NGMwLDAgOS43MDU4LC0xLjcwNDcyIDEyLjM2MjkxLDIuNzY5OTdjMi4zNzUxMiw0LjAwMDA4IDAuMDcxNTUsOC42OTQxOCAwLjIxMjA5LDEwLjEyNzMxYzAuMTQwNTQsMS40MzMxMyAyLjE3NTAzLDEuMTA4OTYgMi4xNzUwMywxLjEwODk2bC0wLjAxMzI2LDEuNjc0MTFjMCwwIC0yLjg2NDQ3LDAuNjY5MDIgLTMuNDExOTksLTEuMzE5MTVjLTAuNTQ3NTMsLTEuOTg4MTYgLTAuNTU5NTIsLTUuOTc4MjkgLTIuODIzNTQsLTguMjI3NzdjLTIuMjY0NjYsLTIuMjQ5MjcgLTcuNTM2ODcsLTAuNzU4NjkgLTcuNTM2ODcsLTAuNzU4NjkiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTI0OS4zNDMxMywxNjQuMjc4MDdjLTAuMzMzNTEsMi4xMTc2MSAtMi4zNTI4NywzLjMwODgxIC00LjE4OTA2LDQuMDAxNThjLTEuNjQ5NzQsMC43MjYxIC0zLjU0OTg5LDEuMDA4MDIgLTUuMjc1NjUsMC4zNjk5Yy0xLjYzMTksLTAuMzgwOTkgLTMuMTg4MjEsLTEuODA2NzYgLTIuOTk0NiwtMy41OTczM2MwLjE1NDQzLC0yLjA4NTA5IDEuODk2NTksLTMuODIyNDMgMy44NDIzOCwtNC40MDQ5OGMxLjkzOTE1LC0wLjc5OTIyIDQuMjUyODcsLTAuNzMwNDYgNi4xMDQ2MSwwLjI1ODgzYzAuOTEyNywwLjQyMjE4IDIuMTg5MDgsLTE0LjE0MDYzIDMuMDM1MDksLTIwLjU4MTk4YzAuMTAwNDksLTAuNjQxOCAyLjU4Mzk4LC0wLjQ3MDkzIDIuNTEyOTMsMC4xNTEwNWMwLDAgLTEuODUzNTQsMTUuNTIyMyAtMy4wMzU3MSwyMy44MDI5MnoiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvZz48L2c+PC9zdmc+"; @@ -28,7 +29,8 @@ const startFlag = "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48ZGVmcz48c3R5bGU+LmNscy0xLC5jbHMtMntmaWxsOiM0Y2JmNTY7c3Ryb2tlOiM0NTk5M2Q7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO30uY2xzLTJ7c3Ryb2tlLXdpZHRoOjEuNXB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tZ3JlZW4tZmxhZzwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNLjc1LDJBNi40NCw2LjQ0LDAsMCwxLDguNDQsMmgwYTYuNDQsNi40NCwwLDAsMCw3LjY5LDBWMTIuNGE2LjQ0LDYuNDQsMCwwLDEtNy42OSwwaDBhNi40NCw2LjQ0LDAsMCwwLTcuNjksMCIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjAuNzUiIHkxPSIxNi43NSIgeDI9IjAuNzUiIHkyPSIwLjc1Ii8+PC9zdmc+"; - // Pizzicato Library (Basically Web Audio API, but with Premade Effects, Stuff) + // Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) + // uses MIT License const scriptElement = document.createElement("script"); scriptElement.textContent = `!function(e){"use strict";function t(e,t){this.options={},e=e||this.options;var i={frequency:350,peak:1};this.inputNode=this.filterNode=s.context.createBiquadFilter(),this.filterNode.type=t,this.outputNode=o.context.createGain(),this.filterNode.connect(this.outputNode);for(var n in i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]}function i(){var e,t,i=s.context.sampleRate*this.time,n=o.context.createBuffer(2,i,s.context.sampleRate),a=n.getChannelData(0),r=n.getChannelData(1);for(t=0;i>t;t++)e=this.reverse?i-t:t,a[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay),r[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay);this.reverbNode.buffer=n}function n(e){for(var t=s.context.sampleRate,i=new Float32Array(t),n=Math.PI/180,o=0;t>o;o++){var a=2*o/t-1;i[o]=(3+e)*a*20*n/(Math.PI+e*Math.abs(a))}return i}var o={},s=o,a="object"==typeof module&&module.exports,r="function"==typeof define&&define.amd;a?module.exports=o:r?define([],o):e.Pizzicato=e.Pz=o;var c=e.AudioContext||e.webkitAudioContext;if(!c)return void console.error("No AudioContext found in this environment. Please ensure your window or global object contains a working AudioContext constructor function.");o.context=new c;var h=o.context.createGain();h.connect(o.context.destination),o.Util={isString:function(e){return"[object String]"===toString.call(e)},isObject:function(e){return"[object Object]"===toString.call(e)},isFunction:function(e){return"[object Function]"===toString.call(e)},isNumber:function(e){return"[object Number]"===toString.call(e)&&e===+e},isArray:function(e){return"[object Array]"===toString.call(e)},isInRange:function(e,t,i){return s.Util.isNumber(e)&&s.Util.isNumber(t)&&s.Util.isNumber(i)?e>=t&&i>=e:!1},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof s.Sound},isEffect:function(e){for(var t in o.Effects)if(e instanceof o.Effects[t])return!0;return!1},normalize:function(e,t,i){return s.Util.isNumber(e)&&s.Util.isNumber(t)&&s.Util.isNumber(i)?(i-t)*e/1+t:void 0},getDryLevel:function(e){return!s.Util.isNumber(e)||e>1||0>e?0:.5>=e?1:1-2*(e-.5)},getWetLevel:function(e){return!s.Util.isNumber(e)||e>1||0>e?0:e>=.5?1:1-2*(.5-e)}};var u=o.context.createGain(),d=Object.getPrototypeOf(Object.getPrototypeOf(u)),l=d.connect;d.connect=function(e){var t=s.Util.isEffect(e)?e.inputNode:e;return l.call(this,t),e},Object.defineProperty(o,"volume",{enumerable:!0,get:function(){return h.gain.value},set:function(e){s.Util.isInRange(e,0,1)&&h&&(h.gain.value=e)}}),Object.defineProperty(o,"masterGainNode",{enumerable:!1,get:function(){return h},set:function(e){console.error("Can't set the master gain node")}}),o.Events={on:function(e,t,i){if(e&&t){this._events=this._events||{};var n=this._events[e]||(this._events[e]=[]);n.push({callback:t,context:i||this,handler:this})}},trigger:function(e){if(e){var t,i,n,o;if(this._events=this._events||{},t=this._events[e]||(this._events[e]=[])){for(i=Math.max(0,arguments.length-1),n=[],o=0;i>o;o++)n[o]=arguments[o+1];for(o=0;o1?(e.shift(),void a(e,t)):(i=i||new Error("Error decoding audio file "+e[0]),void(d.isFunction(t)&&t(i)))}.bind(u))},i.onreadystatechange=function(t){4===i.readyState&&200!==i.status&&console.error("Error while fetching "+e[0]+". "+i.statusText)},i.send()}function r(e,t){return navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,navigator.getUserMedia?void navigator.getUserMedia({audio:!0},function(e){u.getRawSourceNode=function(){return o.context.createMediaStreamSource(e)},d.isFunction(t)&&t()}.bind(u),function(e){d.isFunction(t)&&t(e)}):void console.error("Your browser does not support getUserMedia")}function c(e,t){var i=d.isFunction(e)?e:e.audioFunction,n=d.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!n)try{o.context.createScriptProcessor()}catch(s){n=2048}this.getRawSourceNode=function(){var e=o.context.createScriptProcessor(n,1,1);return e.onaudioprocess=i,e}}function h(e,t){this.getRawSourceNode=e.sound.getRawSourceNode,e.sound.sourceNode&&s.Util.isOscillator(e.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=e.sound.frequency)}var u=this,d=o.Util,l=i(e),f=d.isObject(e)&&d.isObject(e.options),p=.04,v=.04;if(l)throw console.error(l),new Error("Error initializing Pizzicato Sound: "+l);this.detached=f&&e.options.detached,this.masterVolume=o.context.createGain(),this.fadeNode=o.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(o.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=f&&e.options.loop,this.attack=f&&d.isNumber(e.options.attack)?e.options.attack:p,this.volume=f&&d.isNumber(e.options.volume)?e.options.volume:1,f&&d.isNumber(e.options.release)?this.release=e.options.release:f&&d.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=v,e?d.isString(e)?a.bind(this)(e,t):d.isFunction(e)?c.bind(this)(e,t):"file"===e.source?a.bind(this)(e.options.path,t):"wave"===e.source?n.bind(this)(e.options,t):"input"===e.source?r.bind(this)(e,t):"script"===e.source?c.bind(this)(e.options,t):"sound"===e.source&&h.bind(this)(e.options,t):n.bind(this)({},t)},o.Sound.prototype=Object.create(o.Events,{play:{enumerable:!0,value:function(e,t){this.playing||(s.Util.isNumber(t)||(t=this.offsetTime||0),s.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),s.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=o.context.currentTime-t,this.sourceNode.start(s.context.currentTime+e,t)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=s.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/s.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new o.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),t=0;t0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var i=s.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this sound."),this;var i=this.playing;i&&this.pause();var n=0===t?this.fadeNode:this.effectConnectors[t-1];n.disconnect();var o=this.effectConnectors[t];o.disconnect(),e.disconnect(o),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var s;return s=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],n.connect(s),i&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){var e=this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(s.context.currentTime),this.fadeNode.gain.setValueAtTime(e,s.context.currentTime),!this.attack)return void this.fadeNode.gain.setValueAtTime(1,o.context.currentTime);var t=(1-this.fadeNode.gain.value)*this.attack;this.fadeNode.gain.setValueAtTime(this.fadeNode.gain.value,o.context.currentTime),this.fadeNode.gain.linearRampToValueAtTime(1,o.context.currentTime+t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,i=function(){return s.Util.isFunction(t.stop)?t.stop(0):t.disconnect()},n=this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(s.context.currentTime),this.fadeNode.gain.setValueAtTime(n,s.context.currentTime),!this.release)return void i();var a=this.fadeNode.gain.value*this.release;this.fadeNode.gain.setValueAtTime(this.fadeNode.gain.value,o.context.currentTime),this.fadeNode.gain.linearRampToValueAtTime(1e-5,o.context.currentTime+a),window.setTimeout(function(){i()},1e3*a)}}}),o.Group=function(e){e=e||[],this.mergeGainNode=s.context.createGain(),this.masterVolume=s.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(s.masterGainNode);for(var t=0;t-1?void console.warn("The Pizzicato.Sound object was already added to this group"):(e.detached&&console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together."),e.disconnect(s.masterGainNode),e.connect(this.mergeGainNode),void this.sounds.push(e)):void console.error("You can only add Pizzicato.Sound objects")}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);return-1===t?void console.warn("Cannot remove a sound that is not part of this group."):(e.disconnect(this.mergeGainNode),e.connect(s.masterGainNode),void this.sounds.splice(t,1))}},volume:{enumerable:!0,get:function(){return this.masterVolume?this.masterVolume.gain.value:void 0},set:function(e){s.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var i=s.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this group."),this;var i=0===t?this.mergeGainNode:this.effectConnectors(t-1);i.disconnect();var n=this.effectConnectors[t];n.disconnect(),e.disconnect(n),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var o;return o=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],i.connect(o),this}}}),o.Effects={};var f=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});o.Effects.Delay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Delay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),o.Effects.Compressor=function(e){this.options={},e=e||this.options;var t={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};this.inputNode=this.compressorNode=o.context.createDynamicsCompressor(),this.outputNode=o.context.createGain(),this.compressorNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Compressor.prototype=Object.create(f,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){o.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){o.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){o.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){o.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){o.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),o.Effects.LowPassFilter=function(e){t.call(this,e,"lowpass")},o.Effects.HighPassFilter=function(e){t.call(this,e,"highpass")};var p=Object.create(f,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){o.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){o.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});o.Effects.LowPassFilter.prototype=p,o.Effects.HighPassFilter.prototype=p,o.Effects.Distortion=function(e){this.options={},e=e||this.options;var t={gain:.5};this.waveShaperNode=o.context.createWaveShaper(),this.inputNode=this.outputNode=this.waveShaperNode;for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Distortion.prototype=Object.create(f,{gain:{enumerable:!0,get:function(){return this.options.gain},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.gain=e,this.adjustGain())}},adjustGain:{writable:!1,configurable:!1,enumerable:!1,value:function(){for(var e,t=s.Util.isNumber(this.options.gain)?parseInt(100*this.options.gain,10):50,i=44100,n=new Float32Array(i),o=Math.PI/180,a=0;i>a;++a)e=2*a/i-1,n[a]=(3+t)*e*20*o/(Math.PI+t*Math.abs(e));this.waveShaperNode.curve=n}}}),o.Effects.Flanger=function(e){this.options={},e=e||this.options;var t={time:.45,speed:.2,depth:.1,feedback:.1,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.inputFeedbackNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.oscillatorNode=o.context.createOscillator(),this.gainNode=o.context.createGain(),this.feedbackNode=o.context.createGain(),this.oscillatorNode.type="sine",this.inputNode.connect(this.inputFeedbackNode),this.inputNode.connect(this.dryGainNode),this.inputFeedbackNode.connect(this.delayNode),this.inputFeedbackNode.connect(this.wetGainNode),this.delayNode.connect(this.wetGainNode),this.delayNode.connect(this.feedbackNode),this.feedbackNode.connect(this.inputFeedbackNode),this.oscillatorNode.connect(this.gainNode),this.gainNode.connect(this.delayNode.delayTime),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),this.oscillatorNode.start(0);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Flanger.prototype=Object.create(f,{time:{enumberable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.time=e,this.delayNode.delayTime.value=s.Util.normalize(e,.001,.02))}},speed:{enumberable:!0,get:function(){return this.options.speed},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.speed=e,this.oscillatorNode.frequency.value=s.Util.normalize(e,.5,5))}},depth:{enumberable:!0,get:function(){return this.options.depth},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.depth=e,this.gainNode.gain.value=s.Util.normalize(e,5e-4,.005))}},feedback:{enumberable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=e,this.feedbackNode.gain.value=s.Util.normalize(e,0,.8))}},mix:{enumberable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}}}),o.Effects.StereoPanner=function(e){this.options={},e=e||this.options;var t={pan:0};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),o.context.createStereoPanner?(this.pannerNode=o.context.createStereoPanner(),this.inputNode.connect(this.pannerNode),this.pannerNode.connect(this.outputNode)):this.inputNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.StereoPanner.prototype=Object.create(f,{pan:{enumerable:!0,get:function(){return this.options.pan},set:function(e){s.Util.isInRange(e,-1,1)&&(this.options.pan=e,this.pannerNode&&(this.pannerNode.pan.value=e))}}}),o.Effects.Convolver=function(e,t){this.options={},e=e||this.options;var i=this,n=new XMLHttpRequest,a={mix:.5};this.callback=t,this.inputNode=o.context.createGain(),this.convolverNode=o.context.createConvolver(),this.outputNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var r in a)this[r]=e[r],this[r]=void 0===this[r]||null===this[r]?a[r]:this[r];return e.impulse?(n.open("GET",e.impulse,!0),n.responseType="arraybuffer",n.onload=function(e){var t=e.target.response;o.context.decodeAudioData(t,function(e){i.convolverNode.buffer=e,i.callback&&s.Util.isFunction(i.callback)&&i.callback()},function(e){e=e||new Error("Error decoding impulse file"),i.callback&&s.Util.isFunction(i.callback)&&i.callback(e)})},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e.impulse+". "+n.statusText)},void n.send()):void console.error("No impulse file specified.")},o.Effects.Convolver.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}}}),o.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.delayNodeLeft=o.context.createDelay(),this.delayNodeRight=o.context.createDelay(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.channelMerger=o.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.PingPongDelay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),o.Effects.Reverb=function(e){this.options={},e=e||this.options;var t={mix:.5,time:.01,decay:.01,reverse:!1};this.inputNode=o.context.createGain(),this.reverbNode=o.context.createConvolver(),this.outputNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var n in t)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?t[n]:this[n];i.bind(this)()},o.Effects.Reverb.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,1e-4,10)&&(this.options.time=e,i.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){s.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,i.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){s.Util.isBool(e)&&(this.options.reverse=e,i.bind(this)())}}}),o.Effects.Tremolo=function(e){this.options={},e=e||this.options;var t={speed:4,depth:1,mix:.8};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.tremoloGainNode=o.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=o.context.createOscillator(),this.shaperNode=o.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Tremolo.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){s.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),o.Effects.DubDelay=function(e){this.options={},e=e||this.options;var t={feedback:.6,time:.7,mix:.5,cutoff:700};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.bqFilterNode=o.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.DubDelay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){s.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),o.Effects.RingModulator=function(e){this.options={},e=e||this.options;var t={speed:30,distortion:1,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.vIn=o.context.createOscillator(),this.vIn.start(0),this.vInGain=o.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=o.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=o.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new v(o.context),this.vInDiode2=new v(o.context),this.vInInverter3=o.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=o.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new v(o.context),this.vcDiode4=new v(o.context),this.outGain=o.context.createGain(),this.outGain.gain.value=3,this.compressor=o.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]};var v=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};return v.prototype.setDistortion=function(e){return this.h=e,this.setCurve()},v.prototype.setCurve=function(){var e,t,i,n,o,s,a,r;for(t=1024,o=new Float32Array(t),e=s=0,a=o.length;a>=0?a>s:s>a;e=a>=0?++s:--s)i=(e-t/2)/(t/2),i=Math.abs(i),n=i<=this.vb?0:this.vb { - const projectVal = runtime.audioEngine.inputNode.gain.value; + const projectVal = scratchAudio.inputNode.gain.value; Object.keys(soundBank).forEach(key => { const curVol = Math.min(100, Math.max(0, soundBank[key].vol)) / 100; soundBank[key].context.volume = curVol * projectVal; @@ -557,17 +559,23 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ this.deleteSound(args); if (!args.URL) return resolve(); const engine = new Pizzicato.Sound(args.URL, () => { - // Expose Buffers - engine.volume = 0; - engine.play(); - engine.stop(); - engine.volume = 1; - soundBank[args.NAME] = { - context: engine, name: args.NAME, src: args.URL, - effects: {}, vol: 100, gain: 1, pitch: 1, - overlap: false, overlays: [], isBind: false, binds: {} - }; - resolve(); + try { + // Expose Buffers + engine.volume = 0; + engine.play(); + engine.stop(); + engine.volume = 1; + soundBank[args.NAME] = { + context: engine, name: args.NAME, src: args.URL, + effects: {}, vol: 100, gain: 1, pitch: 1, + overlap: false, overlays: [], isBind: false, binds: {} + }; + resolve(); + } catch { + // File is Corrupted / Doesnt Exist / is a unedited Scratch Sound + alert("Tune Shark V3 Cant Import this Sound, File may be Corrupted or Non-Existent"); + resolve(); + } }); }); } From c9142b82d87ceb6343477ef6550a0d687628235d Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:35:32 -0700 Subject: [PATCH 07/40] Tune-Shark-V3 -- V3.1 --- extensions/SharkPool/Tune-Shark-V3.js | 134 ++++++++++++++++---------- 1 file changed, 81 insertions(+), 53 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 68cd528775..f58095d28b 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.0.02 +// Version V.3.1.0 (function (Scratch) { "use strict"; @@ -64,20 +64,31 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ runtime.on("BEFORE_EXECUTE", () => { const projectVal = scratchAudio.inputNode.gain.value; Object.keys(soundBank).forEach(key => { - const curVol = Math.min(100, Math.max(0, soundBank[key].vol)) / 100; - soundBank[key].context.volume = curVol * projectVal; + const bank = soundBank[key]; + const sound = bank.context; + // Clamp Volume to Project Volume + const curVol = Math.min(100, Math.max(0, bank.vol)) / 100; + sound.volume = curVol * projectVal; + + // Apply Speed Changes + if (bank.speed !== 1 && sound.playing) { + const lastplay = sound.lastTimePlayed; + const time = Math.abs(lastplay - sound.sourceNode.context.currentTime); + sound.stop(); + sound.play(0, time * bank.speed); + sound.lastTimePlayed = lastplay; + this.patchLinks(sound.sourceNode, bank); + } }); }); runtime.on("SP_PROJECT_PAUSE", () => { - if (runtime.ioDevices.clock._paused) { - Object.keys(soundBank).forEach(key => { soundBank[key].context.pause() }); - } else { + if (runtime.ioDevices.clock._paused) Object.keys(soundBank).forEach(key => { soundBank[key].context.pause() }); + else { Object.keys(soundBank).forEach(key => { const thisSound = soundBank[key].context; if (thisSound.paused) { thisSound.play(); - thisSound.sourceNode.playbackRate.value = soundBank[key].pitch; - thisSound.sourceNode.gainSuccessor.gain.value = soundBank[key].gain; + this.patchLinks(thisSound.sourceNode, soundBank[key]); } }); } @@ -419,13 +430,13 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ toggleMenu: ["on", "off"], bindMenu: ["bind", "unBind"], typePass: ["highpass", "lowpass"], - singleEffects: ["pitch", "pan", "gain", "distortion"], + singleEffects: ["pitch", "detune", "speed", "pan", "gain", "distortion"], soundProps: { acceptReporters: true, items: [ "length", "current time", "source", "binds", "volume", "pitch", - "pan", "gain", "distortion", "reverb", "delay", "tremolo", - "fuzz", "highpass", "lowpass", "flanger", "compressor" + "detune", "speed", "pan", "gain", "distortion", "reverb", "delay", + "tremolo", "fuzz", "highpass", "lowpass", "flanger", "compressor" ] }, soundBools: { @@ -435,8 +446,8 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ effectMenu: { acceptReporters: true, items: [ - "all effects", "pitch", "pan", "gain", "distortion", "reverb", "delay", - "tremolo", "fuzz", "highpass", "lowpass", "flanger", "compressor" + "all effects", "pitch", "detune", "speed", "pan", "gain", "distortion", "reverb", + "delay", "tremolo", "fuzz", "highpass", "lowpass", "flanger", "compressor" ] } }, @@ -449,11 +460,19 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ return sounds.indexOf(sounds.filter((sound) => { return sound.name === name })[0]); } - calcTime(leng, start, currentT) { - const time = Math.abs(start - currentT); + calcTime(leng, start, currentT, isLoop, loopStart) { + let time = Math.abs(start - currentT); + if (isLoop) return (Math.max(0, time % (leng - loopStart)) + loopStart); return Math.min(leng, Math.max(0, time)); } + modTime(number, opts) { + number = number / opts.pitch; + number = number / opts.speed; + number = number / ((opts.detune / 1000) + 1); + return number; + } + updateEffect(effect, sound, name, args) { delete args.NAME; delete args.TYPE; @@ -509,24 +528,29 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ if (sound.playing && con.overlap) { const clone = sound.clone(); // Clone context to Menu for Control Purposes const newName = `${con.name}_COPY_${Math.random()}`; - soundBank[newName] = { context: clone, vol: con.vol, overlap: false, gain: con.gain, pitch: con.pitch }; + soundBank[newName] = { + ...sound, + context: clone, name: newName, loopParm: [0, 0], overlap: false, + overlays: [], isBind: false, binds: {} + }; clone.play(); clone.sourceNode.playbackRate.value = con.pitch; + clone.sourceNode.gainSuccessor.gain.value = con.gain; con.overlays.push(clone); clone.on("end", function() { delete soundBank[newName] }); } else { - sound.play(0, atTime); - sound.sourceNode.playbackRate.value = con.pitch; - sound.sourceNode.gainSuccessor.gain.value = con.gain; + sound.play(0, sound.loop ? con.loopParm[0] : atTime); + const srcNode = sound.sourceNode; + this.patchLinks(srcNode, con); if (Object.keys(con.binds).length > 0) { Object.keys(con.binds).forEach(key => { const thisSound = con.binds[key]; const context = thisSound.context; context.play(0, atTime); - context.sourceNode.playbackRate.value = thisSound.pitch; - context.sourceNode.gainSuccessor.gain.value = thisSound.gain; + this.patchLinks(context.sourceNode, thisSound); }); } + if (sound.loop) this.loopParams({ NAME : con.name , START : con.loopParm[0], END : con.loopParm[1] }); } } catch { console.warn("Audio has not Loaded Yet, Ignore Next Error"); @@ -543,16 +567,21 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ for (let i = 0; i < sound.overlays.length; i++) { sound.overlays[i].pause() } } else if (type === "play") { sound.context.play(); - sound.context.sourceNode.playbackRate.value = sound.pitch; - sound.context.sourceNode.gainSuccessor.gain.value = sound.gain; + this.patchLinks(sound.context.sourceNode, sound); for (let i = 0; i < sound.overlays.length; i++) { sound.overlays[i].play(); - sound.overlays[i].sourceNode.playbackRate.value = sound.pitch; - sound.overlays[i].sourceNode.gainSuccessor.gain.value = sound.gain; + this.patchLinks(sound.overlays[i].sourceNode, sound); } } } + patchLinks(src, sound) { + src.playbackRate.value = sound.pitch; + src.detune.value = sound.detune; + src.gainSuccessor.gain.value = sound.gain; + if (src.loop) this.loopParams({ NAME : sound.name , START : sound.loopParm[0], END : sound.loopParm[1] }); + } + // Block Funcs importURL(args, util) { return new Promise((resolve) => { @@ -566,8 +595,8 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ engine.stop(); engine.volume = 1; soundBank[args.NAME] = { - context: engine, name: args.NAME, src: args.URL, - effects: {}, vol: 100, gain: 1, pitch: 1, + context: engine, name: args.NAME, src: args.URL, effects: {}, + vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, loopParm: [0, 0], overlap: false, overlays: [], isBind: false, binds: {} }; resolve(); @@ -641,7 +670,6 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ if (sound === undefined) return; const time = Scratch.Cast.toNumber(args.TIME); const max = Scratch.Cast.toNumber(args.MAX); - this.play(sound.context, time, sound); await new Promise((resolve, reject) => { setTimeout(() => { @@ -670,8 +698,7 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ else { Object.keys(soundBank).forEach(key => { soundBank[key].context.play(); - soundBank[key].context.sourceNode.playbackRate.value = soundBank[key].pitch; - soundBank[key].context.sourceNode.gainSuccessor.gain.value = soundBank[key].gain; + this.patchLinks(soundBank[key].context.sourceNode, soundBank[key]); }); } } @@ -688,6 +715,7 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ const sound = soundBank[args.NAME]; if (sound === undefined) return; sound.context.loop = args.TYPE === "on"; + if (args.TYPE === "off") this.typeOverlay(sound, "stop"); } loopParams(args) { @@ -697,6 +725,7 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ const srcNode = sound.context.sourceNode; srcNode.loopStart = Scratch.Cast.toNumber(args.START); srcNode.loopEnd = Scratch.Cast.toNumber(args.END); + sound.loopParm = [srcNode.loopStart, srcNode.loopEnd]; } deleteSound(args) { @@ -738,13 +767,20 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ if (sound === undefined) return 0; const src = sound.context.sourceNode; switch (args.PROP) { - case "length": return src.buffer.duration / sound.pitch; + case "length": return this.modTime(src.buffer.duration, sound); case "current time": return !sound.context.playing ? 0 : - this.calcTime(src.buffer.duration, sound.context.lastTimePlayed, src.context.currentTime, src) / sound.pitch; + this.modTime( + this.calcTime( + sound.context.loop ? sound.loopParm[1] : src.buffer.duration, sound.context.lastTimePlayed, + src.context.currentTime, sound.context.loop, sound.loopParm[0] + ), sound + ); case "source": return sound.src; case "binds": return JSON.stringify(Object.keys(sound.binds)); case "volume": return sound.vol; case "pitch": return Math.round((sound.pitch - 1) * 100); + case "detune": return sound.detune / 10; + case "speed": return sound.speed * 100; case "gain": return sound.gain * 100; case "pan": return sound.effects[args.PROP.toUpperCase()]?.options.pan * 100 || 0; case "distortion": return sound.effects[args.PROP.toUpperCase()]?.options.gain * 100 || 0; @@ -789,47 +825,39 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ resetEffect(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - const oldPitch = sound.context.sourceNode.playbackRate.value; // In-case the Engine Resets it - const oldGain = sound.context.sourceNode.gainSuccessor.gain.value; // In-case the Engine Resets it if (args.EFFECT === "all effects") { const effects = sound.effects; Object.keys(effects).forEach(key => { sound.context.removeEffect(effects[key]) }); sound.effects = {}; } - if (args.EFFECT === "all effects" || args.EFFECT === "pitch") { - sound.context.sourceNode.playbackRate.value = 1; - sound.pitch = 1; - } - if (args.EFFECT === "all effects" || args.EFFECT === "gain") { - sound.context.sourceNode.gainSuccessor.gain.value = 1; - sound.gain = 1; - } + if (args.EFFECT === "all effects" || args.EFFECT === "pitch") sound.pitch = 1; + if (args.EFFECT === "all effects" || args.EFFECT === "detune") sound.detune = 0; + if (args.EFFECT === "all effects" || args.EFFECT === "speed") sound.speed = 1; + if (args.EFFECT === "all effects" || args.EFFECT === "gain") sound.gain = 1; const name = args.EFFECT.toUpperCase(); if (sound.effects[name] !== undefined) { sound.context.removeEffect(sound.effects[name]); delete sound.effects[name]; } - sound.context.sourceNode.playbackRate.value = oldPitch; - sound.context.sourceNode.gainSuccessor.gain.value = oldGain; + this.patchLinks(sound.context.sourceNode, sound); } setThing(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; const value = Scratch.Cast.toNumber(args.VALUE) / 100; - if (args.TYPE === "pitch") { - sound.pitch = Math.max(0, value + 1) - sound.context.sourceNode.playbackRate.value = sound.pitch; - } else if (args.TYPE === "gain") { - sound.gain = value; - sound.context.sourceNode.gainSuccessor.gain.value = value; - } else if (args.TYPE === "pan") { + if (args.TYPE === "pitch") sound.pitch = Math.max(0, value + 1); + else if (args.TYPE === "detune") sound.detune = value * 1000; + else if (args.TYPE === "speed") sound.speed = Math.max(0, value); + else if (args.TYPE === "gain") sound.gain = value; + else if (args.TYPE === "pan") { const pan = new Pizzicato.Effects.StereoPanner({ pan: Math.max(-1, Math.min(1, value)) }); - this.updateEffect(pan, sound, "PAN", args); + return this.updateEffect(pan, sound, "PAN", args); } else { const distort = new Pizzicato.Effects.Distortion({ gain: value }); - this.updateEffect(distort, sound, "DISTORTION", args); + return this.updateEffect(distort, sound, "DISTORTION", args); } + this.patchLinks(sound.context.sourceNode, sound); } setReverb(args) { From bac8d65f6479a48c5fbbac2671ed21eed4caf689 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Tue, 4 Jun 2024 22:24:00 -0700 Subject: [PATCH 08/40] Tune-Shark-V3 -- Current Time Doesnt Need Modding --- extensions/SharkPool/Tune-Shark-V3.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index f58095d28b..c1529c30c5 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,8 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.1.0 +// Version V.3.1.1 +// Thanks to HOME for the song "Resonance" being used as the default audio link (function (Scratch) { "use strict"; @@ -108,7 +109,7 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ text: "import sound from URL [URL] named [NAME]", blockIconURI: settingsIconURI, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, @@ -460,9 +461,11 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ return sounds.indexOf(sounds.filter((sound) => { return sound.name === name })[0]); } - calcTime(leng, start, currentT, isLoop, loopStart) { + calcTime(leng, start, currentT, sound) { + leng = this.modTime(leng, sound); + const loopStart = sound.loopParm[0]; let time = Math.abs(start - currentT); - if (isLoop) return (Math.max(0, time % (leng - loopStart)) + loopStart); + if (sound.context.loop) return (Math.max(0, time % (leng - loopStart)) + loopStart); return Math.min(leng, Math.max(0, time)); } @@ -769,11 +772,9 @@ return this.node.connect(e)},o.Effects.RingModulator.prototype=Object.create(f,{ switch (args.PROP) { case "length": return this.modTime(src.buffer.duration, sound); case "current time": return !sound.context.playing ? 0 : - this.modTime( - this.calcTime( - sound.context.loop ? sound.loopParm[1] : src.buffer.duration, sound.context.lastTimePlayed, - src.context.currentTime, sound.context.loop, sound.loopParm[0] - ), sound + this.calcTime( + sound.context.loop ? sound.loopParm[1] : src.buffer.duration, + sound.context.lastTimePlayed, src.context.currentTime, sound ); case "source": return sound.src; case "binds": return JSON.stringify(Object.keys(sound.binds)); From 4db788ad3a73dc25cf420d14251abf82bc97178a Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Fri, 7 Jun 2024 21:35:39 -0700 Subject: [PATCH 09/40] Update Tune-Shark-V3.js --- extensions/SharkPool/Tune-Shark-V3.js | 63 ++++++++++++++++++++------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index c1529c30c5..55568798ae 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.1.1 +// Version V.3.1.2 // Thanks to HOME for the song "Resonance" being used as the default audio link (function (Scratch) { @@ -30,12 +30,12 @@ const startFlag = "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48ZGVmcz48c3R5bGU+LmNscy0xLC5jbHMtMntmaWxsOiM0Y2JmNTY7c3Ryb2tlOiM0NTk5M2Q7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO30uY2xzLTJ7c3Ryb2tlLXdpZHRoOjEuNXB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tZ3JlZW4tZmxhZzwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNLjc1LDJBNi40NCw2LjQ0LDAsMCwxLDguNDQsMmgwYTYuNDQsNi40NCwwLDAsMCw3LjY5LDBWMTIuNGE2LjQ0LDYuNDQsMCwwLDEtNy42OSwwaDBhNi40NCw2LjQ0LDAsMCwwLTcuNjksMCIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjAuNzUiIHkxPSIxNi43NSIgeDI9IjAuNzUiIHkyPSIwLjc1Ii8+PC9zdmc+"; - // Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) + // Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) -- Modified File // uses MIT License const scriptElement = document.createElement("script"); scriptElement.textContent = -`!function(e){"use strict";function t(e,t){this.options={},e=e||this.options;var i={frequency:350,peak:1};this.inputNode=this.filterNode=s.context.createBiquadFilter(),this.filterNode.type=t,this.outputNode=o.context.createGain(),this.filterNode.connect(this.outputNode);for(var n in i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]}function i(){var e,t,i=s.context.sampleRate*this.time,n=o.context.createBuffer(2,i,s.context.sampleRate),a=n.getChannelData(0),r=n.getChannelData(1);for(t=0;i>t;t++)e=this.reverse?i-t:t,a[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay),r[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay);this.reverbNode.buffer=n}function n(e){for(var t=s.context.sampleRate,i=new Float32Array(t),n=Math.PI/180,o=0;t>o;o++){var a=2*o/t-1;i[o]=(3+e)*a*20*n/(Math.PI+e*Math.abs(a))}return i}var o={},s=o,a="object"==typeof module&&module.exports,r="function"==typeof define&&define.amd;a?module.exports=o:r?define([],o):e.Pizzicato=e.Pz=o;var c=e.AudioContext||e.webkitAudioContext;if(!c)return void console.error("No AudioContext found in this environment. Please ensure your window or global object contains a working AudioContext constructor function.");o.context=new c;var h=o.context.createGain();h.connect(o.context.destination),o.Util={isString:function(e){return"[object String]"===toString.call(e)},isObject:function(e){return"[object Object]"===toString.call(e)},isFunction:function(e){return"[object Function]"===toString.call(e)},isNumber:function(e){return"[object Number]"===toString.call(e)&&e===+e},isArray:function(e){return"[object Array]"===toString.call(e)},isInRange:function(e,t,i){return s.Util.isNumber(e)&&s.Util.isNumber(t)&&s.Util.isNumber(i)?e>=t&&i>=e:!1},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof s.Sound},isEffect:function(e){for(var t in o.Effects)if(e instanceof o.Effects[t])return!0;return!1},normalize:function(e,t,i){return s.Util.isNumber(e)&&s.Util.isNumber(t)&&s.Util.isNumber(i)?(i-t)*e/1+t:void 0},getDryLevel:function(e){return!s.Util.isNumber(e)||e>1||0>e?0:.5>=e?1:1-2*(e-.5)},getWetLevel:function(e){return!s.Util.isNumber(e)||e>1||0>e?0:e>=.5?1:1-2*(.5-e)}};var u=o.context.createGain(),d=Object.getPrototypeOf(Object.getPrototypeOf(u)),l=d.connect;d.connect=function(e){var t=s.Util.isEffect(e)?e.inputNode:e;return l.call(this,t),e},Object.defineProperty(o,"volume",{enumerable:!0,get:function(){return h.gain.value},set:function(e){s.Util.isInRange(e,0,1)&&h&&(h.gain.value=e)}}),Object.defineProperty(o,"masterGainNode",{enumerable:!1,get:function(){return h},set:function(e){console.error("Can't set the master gain node")}}),o.Events={on:function(e,t,i){if(e&&t){this._events=this._events||{};var n=this._events[e]||(this._events[e]=[]);n.push({callback:t,context:i||this,handler:this})}},trigger:function(e){if(e){var t,i,n,o;if(this._events=this._events||{},t=this._events[e]||(this._events[e]=[])){for(i=Math.max(0,arguments.length-1),n=[],o=0;i>o;o++)n[o]=arguments[o+1];for(o=0;o1?(e.shift(),void a(e,t)):(i=i||new Error("Error decoding audio file "+e[0]),void(d.isFunction(t)&&t(i)))}.bind(u))},i.onreadystatechange=function(t){4===i.readyState&&200!==i.status&&console.error("Error while fetching "+e[0]+". "+i.statusText)},i.send()}function r(e,t){return navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,navigator.getUserMedia?void navigator.getUserMedia({audio:!0},function(e){u.getRawSourceNode=function(){return o.context.createMediaStreamSource(e)},d.isFunction(t)&&t()}.bind(u),function(e){d.isFunction(t)&&t(e)}):void console.error("Your browser does not support getUserMedia")}function c(e,t){var i=d.isFunction(e)?e:e.audioFunction,n=d.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!n)try{o.context.createScriptProcessor()}catch(s){n=2048}this.getRawSourceNode=function(){var e=o.context.createScriptProcessor(n,1,1);return e.onaudioprocess=i,e}}function h(e,t){this.getRawSourceNode=e.sound.getRawSourceNode,e.sound.sourceNode&&s.Util.isOscillator(e.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=e.sound.frequency)}var u=this,d=o.Util,l=i(e),f=d.isObject(e)&&d.isObject(e.options),p=.04,v=.04;if(l)throw console.error(l),new Error("Error initializing Pizzicato Sound: "+l);this.detached=f&&e.options.detached,this.masterVolume=o.context.createGain(),this.fadeNode=o.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(o.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=f&&e.options.loop,this.attack=f&&d.isNumber(e.options.attack)?e.options.attack:p,this.volume=f&&d.isNumber(e.options.volume)?e.options.volume:1,f&&d.isNumber(e.options.release)?this.release=e.options.release:f&&d.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=v,e?d.isString(e)?a.bind(this)(e,t):d.isFunction(e)?c.bind(this)(e,t):"file"===e.source?a.bind(this)(e.options.path,t):"wave"===e.source?n.bind(this)(e.options,t):"input"===e.source?r.bind(this)(e,t):"script"===e.source?c.bind(this)(e.options,t):"sound"===e.source&&h.bind(this)(e.options,t):n.bind(this)({},t)},o.Sound.prototype=Object.create(o.Events,{play:{enumerable:!0,value:function(e,t){this.playing||(s.Util.isNumber(t)||(t=this.offsetTime||0),s.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),s.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=o.context.currentTime-t,this.sourceNode.start(s.context.currentTime+e,t)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=s.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/s.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new o.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),t=0;t0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var i=s.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this sound."),this;var i=this.playing;i&&this.pause();var n=0===t?this.fadeNode:this.effectConnectors[t-1];n.disconnect();var o=this.effectConnectors[t];o.disconnect(),e.disconnect(o),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var s;return s=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],n.connect(s),i&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){var e=this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(s.context.currentTime),this.fadeNode.gain.setValueAtTime(e,s.context.currentTime),!this.attack)return void this.fadeNode.gain.setValueAtTime(1,o.context.currentTime);var t=(1-this.fadeNode.gain.value)*this.attack;this.fadeNode.gain.setValueAtTime(this.fadeNode.gain.value,o.context.currentTime),this.fadeNode.gain.linearRampToValueAtTime(1,o.context.currentTime+t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,i=function(){return s.Util.isFunction(t.stop)?t.stop(0):t.disconnect()},n=this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(s.context.currentTime),this.fadeNode.gain.setValueAtTime(n,s.context.currentTime),!this.release)return void i();var a=this.fadeNode.gain.value*this.release;this.fadeNode.gain.setValueAtTime(this.fadeNode.gain.value,o.context.currentTime),this.fadeNode.gain.linearRampToValueAtTime(1e-5,o.context.currentTime+a),window.setTimeout(function(){i()},1e3*a)}}}),o.Group=function(e){e=e||[],this.mergeGainNode=s.context.createGain(),this.masterVolume=s.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(s.masterGainNode);for(var t=0;t-1?void console.warn("The Pizzicato.Sound object was already added to this group"):(e.detached&&console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together."),e.disconnect(s.masterGainNode),e.connect(this.mergeGainNode),void this.sounds.push(e)):void console.error("You can only add Pizzicato.Sound objects")}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);return-1===t?void console.warn("Cannot remove a sound that is not part of this group."):(e.disconnect(this.mergeGainNode),e.connect(s.masterGainNode),void this.sounds.splice(t,1))}},volume:{enumerable:!0,get:function(){return this.masterVolume?this.masterVolume.gain.value:void 0},set:function(e){s.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var i=s.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this group."),this;var i=0===t?this.mergeGainNode:this.effectConnectors(t-1);i.disconnect();var n=this.effectConnectors[t];n.disconnect(),e.disconnect(n),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var o;return o=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],i.connect(o),this}}}),o.Effects={};var f=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});o.Effects.Delay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Delay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),o.Effects.Compressor=function(e){this.options={},e=e||this.options;var t={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};this.inputNode=this.compressorNode=o.context.createDynamicsCompressor(),this.outputNode=o.context.createGain(),this.compressorNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Compressor.prototype=Object.create(f,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){o.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){o.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){o.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){o.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){o.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),o.Effects.LowPassFilter=function(e){t.call(this,e,"lowpass")},o.Effects.HighPassFilter=function(e){t.call(this,e,"highpass")};var p=Object.create(f,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){o.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){o.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});o.Effects.LowPassFilter.prototype=p,o.Effects.HighPassFilter.prototype=p,o.Effects.Distortion=function(e){this.options={},e=e||this.options;var t={gain:.5};this.waveShaperNode=o.context.createWaveShaper(),this.inputNode=this.outputNode=this.waveShaperNode;for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Distortion.prototype=Object.create(f,{gain:{enumerable:!0,get:function(){return this.options.gain},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.gain=e,this.adjustGain())}},adjustGain:{writable:!1,configurable:!1,enumerable:!1,value:function(){for(var e,t=s.Util.isNumber(this.options.gain)?parseInt(100*this.options.gain,10):50,i=44100,n=new Float32Array(i),o=Math.PI/180,a=0;i>a;++a)e=2*a/i-1,n[a]=(3+t)*e*20*o/(Math.PI+t*Math.abs(e));this.waveShaperNode.curve=n}}}),o.Effects.Flanger=function(e){this.options={},e=e||this.options;var t={time:.45,speed:.2,depth:.1,feedback:.1,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.inputFeedbackNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.oscillatorNode=o.context.createOscillator(),this.gainNode=o.context.createGain(),this.feedbackNode=o.context.createGain(),this.oscillatorNode.type="sine",this.inputNode.connect(this.inputFeedbackNode),this.inputNode.connect(this.dryGainNode),this.inputFeedbackNode.connect(this.delayNode),this.inputFeedbackNode.connect(this.wetGainNode),this.delayNode.connect(this.wetGainNode),this.delayNode.connect(this.feedbackNode),this.feedbackNode.connect(this.inputFeedbackNode),this.oscillatorNode.connect(this.gainNode),this.gainNode.connect(this.delayNode.delayTime),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),this.oscillatorNode.start(0);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Flanger.prototype=Object.create(f,{time:{enumberable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.time=e,this.delayNode.delayTime.value=s.Util.normalize(e,.001,.02))}},speed:{enumberable:!0,get:function(){return this.options.speed},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.speed=e,this.oscillatorNode.frequency.value=s.Util.normalize(e,.5,5))}},depth:{enumberable:!0,get:function(){return this.options.depth},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.depth=e,this.gainNode.gain.value=s.Util.normalize(e,5e-4,.005))}},feedback:{enumberable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=e,this.feedbackNode.gain.value=s.Util.normalize(e,0,.8))}},mix:{enumberable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}}}),o.Effects.StereoPanner=function(e){this.options={},e=e||this.options;var t={pan:0};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),o.context.createStereoPanner?(this.pannerNode=o.context.createStereoPanner(),this.inputNode.connect(this.pannerNode),this.pannerNode.connect(this.outputNode)):this.inputNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.StereoPanner.prototype=Object.create(f,{pan:{enumerable:!0,get:function(){return this.options.pan},set:function(e){s.Util.isInRange(e,-1,1)&&(this.options.pan=e,this.pannerNode&&(this.pannerNode.pan.value=e))}}}),o.Effects.Convolver=function(e,t){this.options={},e=e||this.options;var i=this,n=new XMLHttpRequest,a={mix:.5};this.callback=t,this.inputNode=o.context.createGain(),this.convolverNode=o.context.createConvolver(),this.outputNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var r in a)this[r]=e[r],this[r]=void 0===this[r]||null===this[r]?a[r]:this[r];return e.impulse?(n.open("GET",e.impulse,!0),n.responseType="arraybuffer",n.onload=function(e){var t=e.target.response;o.context.decodeAudioData(t,function(e){i.convolverNode.buffer=e,i.callback&&s.Util.isFunction(i.callback)&&i.callback()},function(e){e=e||new Error("Error decoding impulse file"),i.callback&&s.Util.isFunction(i.callback)&&i.callback(e)})},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e.impulse+". "+n.statusText)},void n.send()):void console.error("No impulse file specified.")},o.Effects.Convolver.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}}}),o.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.delayNodeLeft=o.context.createDelay(),this.delayNodeRight=o.context.createDelay(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.channelMerger=o.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.PingPongDelay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),o.Effects.Reverb=function(e){this.options={},e=e||this.options;var t={mix:.5,time:.01,decay:.01,reverse:!1};this.inputNode=o.context.createGain(),this.reverbNode=o.context.createConvolver(),this.outputNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var n in t)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?t[n]:this[n];i.bind(this)()},o.Effects.Reverb.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,1e-4,10)&&(this.options.time=e,i.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){s.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,i.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){s.Util.isBool(e)&&(this.options.reverse=e,i.bind(this)())}}}),o.Effects.Tremolo=function(e){this.options={},e=e||this.options;var t={speed:4,depth:1,mix:.8};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.tremoloGainNode=o.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=o.context.createOscillator(),this.shaperNode=o.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.Tremolo.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){s.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),o.Effects.DubDelay=function(e){this.options={},e=e||this.options;var t={feedback:.6,time:.7,mix:.5,cutoff:700};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.feedbackGainNode=o.context.createGain(),this.delayNode=o.context.createDelay(),this.bqFilterNode=o.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},o.Effects.DubDelay.prototype=Object.create(f,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=o.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=o.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){s.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){s.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){s.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),o.Effects.RingModulator=function(e){this.options={},e=e||this.options;var t={speed:30,distortion:1,mix:.5};this.inputNode=o.context.createGain(),this.outputNode=o.context.createGain(),this.dryGainNode=o.context.createGain(),this.wetGainNode=o.context.createGain(),this.vIn=o.context.createOscillator(),this.vIn.start(0),this.vInGain=o.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=o.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=o.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new v(o.context),this.vInDiode2=new v(o.context),this.vInInverter3=o.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=o.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new v(o.context),this.vcDiode4=new v(o.context),this.outGain=o.context.createGain(),this.outGain.gain.value=3,this.compressor=o.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]};var v=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};return v.prototype.setDistortion=function(e){return this.h=e,this.setCurve()},v.prototype.setCurve=function(){var e,t,i,n,o,s,a,r;for(t=1024,o=new Float32Array(t),e=s=0,a=o.length;a>=0?a>s:s>a;e=a>=0?++s:--s)i=(e-t/2)/(t/2),i=Math.abs(i),n=i<=this.vb?0:this.vbi;i++)t[i].setDistortion(e)}}}}),o.Effects.Quadrafuzz=function(e){this.options={},e=e||this.options;var t={lowGain:.6,midLowGain:.8,midHighGain:.5,highGain:.6};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.lowpassLeft=s.context.createBiquadFilter(),this.lowpassLeft.type="lowpass",this.lowpassLeft.frequency.value=147,this.lowpassLeft.Q.value=.7071,this.bandpass1Left=s.context.createBiquadFilter(),this.bandpass1Left.type="bandpass",this.bandpass1Left.frequency.value=587,this.bandpass1Left.Q.value=.7071,this.bandpass2Left=s.context.createBiquadFilter(),this.bandpass2Left.type="bandpass",this.bandpass2Left.frequency.value=2490,this.bandpass2Left.Q.value=.7071,this.highpassLeft=s.context.createBiquadFilter(),this.highpassLeft.type="highpass",this.highpassLeft.frequency.value=4980,this.highpassLeft.Q.value=.7071,this.overdrives=[];for(var i=0;4>i;i++)this.overdrives[i]=s.context.createWaveShaper(),this.overdrives[i].curve=n();this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode);var o=[this.lowpassLeft,this.bandpass1Left,this.bandpass2Left,this.highpassLeft];for(i=0;it;t++)e=this.reverse?i-t:t,o[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay),r[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay);this.reverbNode.buffer&&(this.inputNode.disconnect(this.reverbNode),this.reverbNode.disconnect(this.wetGainNode),this.reverbNode=s.context.createConvolver(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode)),this.reverbNode.buffer=n}function n(e){this.options={},e=e||this.options;var t={cutoff_frequency_low:100,cutoff_frequency_high:8e3,low_band_gain:1,mid_band_gain:1,high_band_gain:1,low_peak:1,mid_peak:1,high_peak:1};this.inputNode=a.context.createGain(),this.outputNode=a.context.createGain(),this.lowFilterNode=a.context.createBiquadFilter(),this.lowFilterNode.type="lowpass",this.inputNode.connect(this.lowFilterNode),this.lowGainNode=a.context.createGain(),this.lowFilterNode.connect(this.lowGainNode),this.midFilterNode=a.context.createBiquadFilter(),this.midFilterNode.type="bandpass",this.inputNode.connect(this.midFilterNode),this.midGainNode=a.context.createGain(),this.midFilterNode.connect(this.midGainNode),this.highFilterNode=a.context.createBiquadFilter(),this.highFilterNode.type="highpass",this.inputNode.connect(this.highFilterNode),this.highGainNode=a.context.createGain(),this.highFilterNode.connect(this.highGainNode),this.analyserNode=a.context.createAnalyser(),this.lowGainNode.connect(this.analyserNode),this.midGainNode.connect(this.analyserNode),this.highGainNode.connect(this.analyserNode),this.analyserNode.connect(this.outputNode),this.analyserNode.minDecibels=-90,this.analyserNode.maxDecibels=15,this.analyserNode.smoothingTimeConstant=.85,this.analyserNode.fftSize=256,this.options.cutoff_frequency_low=t.cutoff_frequency_low,this.options.cutoff_frequency_high=t.cutoff_frequency_high;for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]}function o(e){for(var t=a.context.sampleRate,i=new Float32Array(t),n=Math.PI/180,o=0;t>o;o++){var s=2*o/t-1;i[o]=(3+e)*s*20*n/(Math.PI+e*Math.abs(s))}return i}var s={},a=s,r="object"==typeof module&&module.exports,c="function"==typeof define&&define.amd;r?module.exports=s:c?define([],s):e.Pizzicato=e.Pz=s;var h=e.AudioContext||e.webkitAudioContext;if(!h)return void console.error("No AudioContext found in this environment. Please ensure your window or global object contains a working AudioContext constructor function.");s.context=new h;var u=s.context.createGain();u.connect(s.context.destination),s.Util={isString:function(e){return"[object String]"===toString.call(e)},isObject:function(e){return"[object Object]"===toString.call(e)},isFunction:function(e){return"[object Function]"===toString.call(e)},isNumber:function(e){return"[object Number]"===toString.call(e)&&e===+e},isArray:function(e){return"[object Array]"===toString.call(e)},isInRange:function(e,t,i){return a.Util.isNumber(e)&&a.Util.isNumber(t)&&a.Util.isNumber(i)?e>=t&&i>=e:!1},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof a.Sound},isEffect:function(e){for(var t in s.Effects)if(e instanceof s.Effects[t])return!0;return!1},normalize:function(e,t,i){return a.Util.isNumber(e)&&a.Util.isNumber(t)&&a.Util.isNumber(i)?(i-t)*e/1+t:void 0},getDryLevel:function(e){return!a.Util.isNumber(e)||e>1||0>e?0:.5>=e?1:1-2*(e-.5)},getWetLevel:function(e){return!a.Util.isNumber(e)||e>1||0>e?0:e>=.5?1:1-2*(.5-e)}};var d=s.context.createGain(),l=Object.getPrototypeOf(Object.getPrototypeOf(d)),f=l.connect;l.connect=function(e){var t=a.Util.isEffect(e)?e.inputNode:e;return f.call(this,t),e},Object.defineProperty(s,"volume",{enumerable:!0,get:function(){return u.gain.value},set:function(e){a.Util.isInRange(e,0,1)&&u&&(u.gain.value=e)}}),Object.defineProperty(s,"masterGainNode",{enumerable:!1,get:function(){return u},set:function(e){console.error("Can't set the master gain node")}}),s.Events={on:function(e,t,i){if(e&&t){this._events=this._events||{};var n=this._events[e]||(this._events[e]=[]);n.push({callback:t,context:i||this,handler:this})}},trigger:function(e){if(e){var t,i,n,o;if(this._events=this._events||{},t=this._events[e]||(this._events[e]=[])){for(i=Math.max(0,arguments.length-1),n=[],o=0;i>o;o++)n[o]=arguments[o+1];for(o=0;o1?(e.shift(),void o(e,t)):(i=i||new Error("Error decoding audio file "+e[0]),void(d.isFunction(t)&&t(i)))}.bind(u))},i.onreadystatechange=function(t){4===i.readyState&&200!==i.status&&console.error("Error while fetching "+e[0]+". "+i.statusText)},i.send()}function r(e,t){if(navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,!navigator.getUserMedia&&(!navigator.mediaDevices||navigator.mediaDevices.getUserMedia))return void console.error("Your browser does not support getUserMedia. Note that the current document must be loaded securely for this to work");var i=function(e){u.getRawSourceNode=function(){return s.context.createMediaStreamSource(e)},d.isFunction(t)&&t()}.bind(u),n=function(e){d.isFunction(t)&&t(e)};navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia({audio:!0}).then(i)["catch"](n):navigator.getUserMedia({audio:!0},i,n)}function c(e,t){var i=d.isFunction(e)?e:e.audioFunction,n=d.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!n)try{s.context.createScriptProcessor()}catch(o){n=2048}this.getRawSourceNode=function(){var e=s.context.createScriptProcessor(n,1,1);return e.onaudioprocess=i,e}}function h(e,t){this.getRawSourceNode=e.sound.getRawSourceNode,e.sound.sourceNode&&a.Util.isOscillator(e.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=e.sound.frequency)}var u=this,d=s.Util,l=i(e),f=d.isObject(e)&&d.isObject(e.options),p=.04,v=.04;if(l)throw console.error(l),new Error("Error initializing Pizzicato Sound: "+l);this.detached=f&&e.options.detached,this.masterVolume=s.context.createGain(),this.fadeNode=s.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(s.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=f&&e.options.loop,this.attack=f&&d.isNumber(e.options.attack)?e.options.attack:p,this.volume=f&&d.isNumber(e.options.volume)?e.options.volume:1,f&&d.isNumber(e.options.release)?this.release=e.options.release:f&&d.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=v,e?d.isString(e)?o.bind(this)(e,t):d.isFunction(e)?c.bind(this)(e,t):"file"===e.source?o.bind(this)(e.options.path,t):"wave"===e.source?n.bind(this)(e.options,t):"input"===e.source?r.bind(this)(e,t):"script"===e.source?c.bind(this)(e.options,t):"sound"===e.source&&h.bind(this)(e.options,t):n.bind(this)({},t)},s.Sound.prototype=Object.create(s.Events,{play:{enumerable:!0,value:function(e,t){this.playing||(a.Util.isNumber(t)||(t=this.offsetTime||0),a.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),a.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=s.context.currentTime-t,this.sourceNode.start(a.context.currentTime+e,t)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=a.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/a.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new s.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),t=0;t0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var i=a.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this sound."),this;var i=this.playing;i&&this.pause();var n=0===t?this.fadeNode:this.effectConnectors[t-1];n.disconnect();var o=this.effectConnectors[t];o.disconnect(),e.disconnect(o),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var s;return s=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],n.connect(s),i&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(a.context.currentTime),!this.attack)return void this.fadeNode.gain.setTargetAtTime(1,a.context.currentTime,.001);var e=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,t=this.attack;e||(t=(1-this.fadeNode.gain.value)*this.attack),this.fadeNode.gain.setTargetAtTime(1,a.context.currentTime,2*t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,i=function(){return a.Util.isFunction(t.stop)?t.stop(0):t.disconnect()};this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(a.context.currentTime),!this.release)return this.fadeNode.gain.setTargetAtTime(0,a.context.currentTime,.001),void i();var n=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,o=this.release;n||(o=this.fadeNode.gain.value*this.release),this.fadeNode.gain.setTargetAtTime(1e-5,a.context.currentTime,o/5),window.setTimeout(function(){i()},1e3*o)}}}),s.Group=function(e){e=e||[],this.mergeGainNode=a.context.createGain(),this.masterVolume=a.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(a.masterGainNode);for(var t=0;t-1?void console.warn("The Pizzicato.Sound object was already added to this group"):e.detached?void console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together."):(e.disconnect(a.masterGainNode),e.connect(this.mergeGainNode),void this.sounds.push(e)):void console.error("You can only add Pizzicato.Sound objects")}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);return-1===t?void console.warn("Cannot remove a sound that is not part of this group."):(e.disconnect(this.mergeGainNode),e.connect(a.masterGainNode),void this.sounds.splice(t,1))}},volume:{enumerable:!0,get:function(){return this.masterVolume?this.masterVolume.gain.value:void 0},set:function(e){a.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var i=a.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this group."),this;var i=0===t?this.mergeGainNode:this.effectConnectors[t-1];i.disconnect();var n=this.effectConnectors[t];n.disconnect(),e.disconnect(n),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var o;return o=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],i.connect(o),this}}}),s.Effects={};var p=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});s.Effects.Delay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.feedbackGainNode=s.context.createGain(),this.delayNode=s.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Delay.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),s.Effects.Compressor=function(e){this.options={},e=e||this.options;var t={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};this.inputNode=this.compressorNode=s.context.createDynamicsCompressor(),this.outputNode=s.context.createGain(),this.compressorNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Compressor.prototype=Object.create(p,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){s.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){s.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){s.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){s.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){s.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),s.Effects.LowPassFilter=function(e){t.call(this,e,"lowpass")},s.Effects.HighPassFilter=function(e){t.call(this,e,"highpass")};var v=Object.create(p,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){s.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){s.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});s.Effects.LowPassFilter.prototype=v,s.Effects.HighPassFilter.prototype=v,s.Effects.Distortion=function(e){this.options={},e=e||this.options;var t={gain:.5};this.waveShaperNode=s.context.createWaveShaper(),this.inputNode=this.outputNode=this.waveShaperNode;for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Distortion.prototype=Object.create(p,{gain:{enumerable:!0,get:function(){return this.options.gain},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.gain=e,this.adjustGain())}},adjustGain:{writable:!1,configurable:!1,enumerable:!1,value:function(){for(var e,t=a.Util.isNumber(this.options.gain)?parseInt(100*this.options.gain,10):50,i=44100,n=new Float32Array(i),o=Math.PI/180,s=0;i>s;++s)e=2*s/i-1,n[s]=(3+t)*e*20*o/(Math.PI+t*Math.abs(e));this.waveShaperNode.curve=n}}}),s.Effects.Flanger=function(e){this.options={},e=e||this.options;var t={time:.45,speed:.2,depth:.1,feedback:.1,mix:.5};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.inputFeedbackNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.delayNode=s.context.createDelay(),this.oscillatorNode=s.context.createOscillator(),this.gainNode=s.context.createGain(),this.feedbackNode=s.context.createGain(),this.oscillatorNode.type="sine",this.inputNode.connect(this.inputFeedbackNode),this.inputNode.connect(this.dryGainNode),this.inputFeedbackNode.connect(this.delayNode),this.inputFeedbackNode.connect(this.wetGainNode),this.delayNode.connect(this.wetGainNode),this.delayNode.connect(this.feedbackNode),this.feedbackNode.connect(this.inputFeedbackNode),this.oscillatorNode.connect(this.gainNode),this.gainNode.connect(this.delayNode.delayTime),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),this.oscillatorNode.start(0);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Flanger.prototype=Object.create(p,{time:{enumberable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.time=e,this.delayNode.delayTime.value=a.Util.normalize(e,.001,.02))}},speed:{enumberable:!0,get:function(){return this.options.speed},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.speed=e,this.oscillatorNode.frequency.value=a.Util.normalize(e,.5,5))}},depth:{enumberable:!0,get:function(){return this.options.depth},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.depth=e,this.gainNode.gain.value=a.Util.normalize(e,5e-4,.005))}},feedback:{enumberable:!0,get:function(){return this.options.feedback},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.feedback=e,this.feedbackNode.gain.value=a.Util.normalize(e,0,.8))}},mix:{enumberable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}}}),s.Effects.StereoPanner=function(e){this.options={},e=e||this.options;var t={pan:0};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),s.context.createStereoPanner?(this.pannerNode=s.context.createStereoPanner(),this.inputNode.connect(this.pannerNode),this.pannerNode.connect(this.outputNode)):s.context.createPanner?(console.warn("Your browser does not support the StereoPannerNode. Will use PannerNode instead."),this.pannerNode=s.context.createPanner(),this.pannerNode.type="equalpower",this.inputNode.connect(this.pannerNode),this.pannerNode.connect(this.outputNode)):(console.warn("Your browser does not support the Panner effect."),this.inputNode.connect(this.outputNode));for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.StereoPanner.prototype=Object.create(p,{pan:{enumerable:!0,get:function(){return this.options.pan},set:function(e){if(a.Util.isInRange(e,-1,1)&&(this.options.pan=e,this.pannerNode)){var t=this.pannerNode.toString().indexOf("StereoPannerNode")>-1;t?this.pannerNode.pan.value=e:this.pannerNode.setPosition(e,0,1-Math.abs(e))}}}}),s.Effects.Convolver=function(e,t){this.options={},e=e||this.options;var i=this,n=new XMLHttpRequest,o={mix:.5};this.callback=t,this.inputNode=s.context.createGain(),this.convolverNode=s.context.createConvolver(),this.outputNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var r in o)this[r]=e[r],this[r]=void 0===this[r]||null===this[r]?o[r]:this[r];return e.impulse?(n.open("GET",e.impulse,!0),n.responseType="arraybuffer",n.onload=function(e){var t=e.target.response;s.context.decodeAudioData(t,function(e){i.convolverNode.buffer=e,i.callback&&a.Util.isFunction(i.callback)&&i.callback()},function(e){e=e||new Error("Error decoding impulse file"),i.callback&&a.Util.isFunction(i.callback)&&i.callback(e)})},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e.impulse+". "+n.statusText)},void n.send()):void console.error("No impulse file specified.")},s.Effects.Convolver.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}}}),s.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.delayNodeLeft=s.context.createDelay(),this.delayNodeRight=s.context.createDelay(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.feedbackGainNode=s.context.createGain(),this.channelMerger=s.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.PingPongDelay.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),s.Effects.Reverb=function(e){this.options={},e=e||this.options;var t={mix:.5,time:.01,decay:.01,reverse:!1};this.inputNode=s.context.createGain(),this.reverbNode=s.context.createConvolver(),this.outputNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var n in t)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?t[n]:this[n];i.bind(this)()},s.Effects.Reverb.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,1e-4,10)&&(this.options.time=e,i.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){a.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,i.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){a.Util.isBool(e)&&(this.options.reverse=e,i.bind(this)())}}}),s.Effects.ThreeBandEqualizer=function(e){n.call(this,e)};var N=Object.create(p,{cutoff_frequency_low:{enumerable:!0,get:function(){return this.options.cutoff_frequency_low},set:function(e){s.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_low=e,this.lowFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},cutoff_frequency_high:{enumerable:!0,get:function(){return this.options.cutoff_frequency_high},set:function(e){s.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_high=e,this.highFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},low_band_gain:{enumerable:!0,get:function(){return this.options.low_band_gain},set:function(e){s.Util.isInRange(e,-40,15)&&(this.options.low_band_gain=e,this.lowGainNode.gain.value=Math.pow(10,e/20))}},mid_band_gain:{enumerable:!0,get:function(){return this.options.mid_band_gain},set:function(e){s.Util.isInRange(e,-40,15)&&(this.options.mid_band_gain=e,this.midGainNode.gain.value=Math.pow(10,e/20))}},high_band_gain:{enumerable:!0,get:function(){return this.options.high_band_gain},set:function(e){s.Util.isInRange(e,-40,15)&&(this.options.high_band_gain=e,this.highGainNode.gain.value=Math.pow(10,e/20))}},low_peak:{enumerable:!0,get:function(){return this.lowFilterNode.Q.value},set:function(e){s.Util.isInRange(e,1e-4,100)&&(this.lowFilterNode.Q.value=e)}},mid_peak:{enumerable:!0,get:function(){return this.midFilterNode.Q.value},set:function(e){s.Util.isInRange(e,1e-4,100)&&(this.midFilterNode.Q.value=e)}},high_peak:{enumerable:!0,get:function(){return this.highFilterNode.Q.value},set:function(e){s.Util.isInRange(e,1e-4,1e3)&&(this.highFilterNode.Q.value=e)}},visualizerBinCount:{enumerable:!0,get:function(){return this.analyserNode.frequencyBinCount},set:function(e){s.Util.isInRange(e,16,1024)&&(this.analyzerNode.fftSize=e)}},analyser:{enumerable:!0,get:function(){return this.analyserNode}},frequencyData:{enumerable:!0,get:function(){return void 0===this.byteFrequencyData&&(this.byteFrequencyData=new Uint8Array(this.analyserNode.frequencyBinCount.value)),this.analyserNode.getByteFrequencyData(this.FrequencyData),this.byteFrequencyData}}});s.Effects.ThreeBandEqualizer.prototype=N,s.Effects.Tremolo=function(e){this.options={},e=e||this.options;var t={speed:4,depth:1,mix:.8};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.tremoloGainNode=s.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=s.context.createOscillator(),this.shaperNode=s.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Tremolo.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){ +a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){a.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),s.Effects.DubDelay=function(e){this.options={},e=e||this.options;var t={feedback:.6,time:.7,mix:.5,cutoff:700};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.feedbackGainNode=s.context.createGain(),this.delayNode=s.context.createDelay(),this.bqFilterNode=s.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.DubDelay.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){a.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),s.Effects.RingModulator=function(e){this.options={},e=e||this.options;var t={speed:30,distortion:1,mix:.5};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.vIn=s.context.createOscillator(),this.vIn.start(0),this.vInGain=s.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=s.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=s.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new g(s.context),this.vInDiode2=new g(s.context),this.vInInverter3=s.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=s.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new g(s.context),this.vcDiode4=new g(s.context),this.outGain=s.context.createGain(),this.outGain.gain.value=3,this.compressor=s.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]};var g=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};return g.prototype.setDistortion=function(e){return this.h=e,this.setCurve()},g.prototype.setCurve=function(){var e,t,i,n,o,s,a,r;for(t=1024,o=new Float32Array(t),e=s=0,a=o.length;a>=0?a>s:s>a;e=a>=0?++s:--s)i=(e-t/2)/(t/2),i=Math.abs(i),n=i<=this.vb?0:this.vbi;i++)t[i].setDistortion(e)}}}}),s.Effects.Quadrafuzz=function(e){this.options={},e=e||this.options;var t={lowGain:.6,midLowGain:.8,midHighGain:.5,highGain:.6};this.inputNode=a.context.createGain(),this.outputNode=a.context.createGain(),this.dryGainNode=a.context.createGain(),this.wetGainNode=a.context.createGain(),this.lowpassLeft=a.context.createBiquadFilter(),this.lowpassLeft.type="lowpass",this.lowpassLeft.frequency.value=147,this.lowpassLeft.Q.value=.7071,this.bandpass1Left=a.context.createBiquadFilter(),this.bandpass1Left.type="bandpass",this.bandpass1Left.frequency.value=587,this.bandpass1Left.Q.value=.7071,this.bandpass2Left=a.context.createBiquadFilter(),this.bandpass2Left.type="bandpass",this.bandpass2Left.frequency.value=2490,this.bandpass2Left.Q.value=.7071,this.highpassLeft=a.context.createBiquadFilter(),this.highpassLeft.type="highpass",this.highpassLeft.frequency.value=4980,this.highpassLeft.Q.value=.7071,this.overdrives=[];for(var i=0;4>i;i++)this.overdrives[i]=a.context.createWaveShaper(),this.overdrives[i].curve=o();this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode);var n=[this.lowpassLeft,this.bandpass1Left,this.bandpass2Left,this.highpassLeft];for(i=0;i Date: Sun, 9 Jun 2024 00:05:21 -0700 Subject: [PATCH 10/40] Update Tune-Shark-V3.js --- extensions/SharkPool/Tune-Shark-V3.js | 92 +++++++++++++++++---------- 1 file changed, 60 insertions(+), 32 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 55568798ae..a4522c6650 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,8 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.1.2 -// Thanks to HOME for the song "Resonance" being used as the default audio link +// Version V.3.1.21 (function (Scratch) { "use strict"; @@ -52,33 +51,50 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. }); let soundBank = {}; - let flagCtrl = false; + let settings = { flagCtrl : false, canSave : false }; + const load = () => { + const storage = runtime.extensionStorage["SPtuneShark3"]; + if (storage === undefined) return; + settings = storage.settings; + soundBank = storage.bank; + for (const item in soundBank) { + soundBank[item].loaded = false; + const engine = new Pizzicato.Sound(soundBank[item].src, () => { + engine.sourceNode = engine.getSourceNode(); + soundBank[item].context = engine; + soundBank[item].loaded = true; + }); + } + }; + load(); class SPtuneShark3 { constructor() { runtime.on("PROJECT_START", () => { - if (flagCtrl) this.ctrlSounds({ CONTROL : "stop" }); + if (settings.flagCtrl) this.ctrlSounds({ CONTROL : "stop" }); }); runtime.on("PROJECT_STOP_ALL", () => { - if (flagCtrl) this.ctrlSounds({ CONTROL : "stop" }); + if (settings.flagCtrl) this.ctrlSounds({ CONTROL : "stop" }); }); runtime.on("BEFORE_EXECUTE", () => { const projectVal = scratchAudio.inputNode.gain.value; Object.keys(soundBank).forEach(key => { const bank = soundBank[key]; - const sound = bank.context; - // Clamp Volume to Project Volume - const curVol = Math.min(100, Math.max(0, bank.vol)) / 100; - sound.volume = curVol * projectVal; - - // Apply Speed Changes - if (bank.speed !== 1 && sound.playing) { - const lastplay = sound.lastTimePlayed; - const time = Math.abs(lastplay - sound.sourceNode.context.currentTime); - sound.stop(); - sound.play(0, time * bank.speed); - sound.lastTimePlayed = lastplay; - this.patchLinks(sound.sourceNode, bank); + if (bank.loaded) { + const sound = bank.context; + // Clamp Volume to Project Volume + const curVol = Math.min(100, Math.max(0, bank.vol)) / 100; + sound.volume = curVol * projectVal; + + // Apply Speed Changes + if (bank.speed !== 1 && sound.playing) { + const lastplay = sound.lastTimePlayed; + const time = Math.abs(lastplay - sound.sourceNode.context.currentTime); + sound.stop(); + sound.play(0, time * bank.speed); + sound.lastTimePlayed = lastplay; + this.patchLinks(sound.sourceNode, bank); + } } }); }); @@ -109,7 +125,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. text: "import sound from URL [URL] named [NAME]", blockIconURI: settingsIconURI, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, @@ -144,6 +160,15 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound2" } }, }, + { + opcode: "save2Project", + blockType: Scratch.BlockType.COMMAND, + text: "[SAVE] all sounds to project", + blockIconURI: settingsIconURI, + arguments: { + SAVE: { type: Scratch.ArgumentType.STRING, menu: "saveMenu" } + }, + }, { blockType: Scratch.BlockType.LABEL, text: "Audio Playback" }, { opcode: "startSound", @@ -441,6 +466,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. } ], menus: { + saveMenu: ["save", "dont save"], un_pauseMenu: ["pause", "unpause"], playMenu: ["start", "stop", "pause", "unpause"], toggleMenu: ["on", "off"], @@ -486,10 +512,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. } modTime(number, opts) { - number = number / opts.pitch; - number = number / opts.speed; - number = number / ((opts.detune / 1000) + 1); - return number; + return number / (opts.pitch * opts.speed * ((opts.detune / 1000) + 1)); } updateEffect(effect, sound, name, args) { @@ -608,15 +631,11 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. if (!args.URL) return resolve(); const engine = new Pizzicato.Sound(args.URL, () => { try { - // Expose Buffers - engine.volume = 0; - engine.play(); - engine.stop(); - engine.volume = 1; + engine.sourceNode = engine.getSourceNode(); soundBank[args.NAME] = { context: engine, name: args.NAME, src: args.URL, effects: {}, - vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, loopParm: [0, 0], - overlap: false, overlays: [], isBind: false, binds: {} + loaded: true, vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, + loopParm: [0, 0], overlap: false, overlays: [], isBind: false, binds: {} }; resolve(); } catch { @@ -722,7 +741,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. } } - enableControl(args) { flagCtrl = args.ON_OFF === "on" } + enableControl(args) { settings.flagCtrl = args.ON_OFF === "on" } toggleOverlap(args) { const sound = soundBank[args.NAME]; @@ -771,7 +790,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. const sound = soundBank[args.NAME]; if (sound === undefined) return false; switch (args.CONTROL) { - case "exists": return true; + case "exists": return sound.loaded; case "playing": return sound.context.playing; case "paused": return sound.context.paused; case "looped": return sound.context.loop; @@ -975,6 +994,15 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. }); this.updateEffect(equalizer, sound, "EQUALIZER", args); } + + save2Project(args) { + settings.canSave = args.SAVE === "save"; + if (settings.canSave) { + const convertBank = JSON.parse(JSON.stringify(soundBank)); + Object.values(convertBank).forEach(item => delete item.context); + runtime.extensionStorage["SPtuneShark3"] = { bank : convertBank, settings }; + } else { runtime.extensionStorage["SPtuneShark3"] = {} } + } } Scratch.extensions.register(new SPtuneShark3()); From 9273e556b36b9ecfc4330283d2625832144c7a32 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 9 Jun 2024 00:25:43 -0700 Subject: [PATCH 11/40] Tune-Shark-V3 -- Project Storage --- extensions/SharkPool/Tune-Shark-V3.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index a4522c6650..063eb619ab 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -52,8 +52,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. let soundBank = {}; let settings = { flagCtrl : false, canSave : false }; - const load = () => { - const storage = runtime.extensionStorage["SPtuneShark3"]; + const load = (storage) => { if (storage === undefined) return; settings = storage.settings; soundBank = storage.bank; @@ -66,7 +65,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. }); } }; - load(); + load(runtime.extensionStorage["SPtuneShark3"]); class SPtuneShark3 { constructor() { @@ -1001,7 +1000,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. const convertBank = JSON.parse(JSON.stringify(soundBank)); Object.values(convertBank).forEach(item => delete item.context); runtime.extensionStorage["SPtuneShark3"] = { bank : convertBank, settings }; - } else { runtime.extensionStorage["SPtuneShark3"] = {} } + } else { runtime.extensionStorage["SPtuneShark3"] = undefined } } } From b25bad9e6abacd39ece0b5be4f25f9e7518b152c Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:24:52 -0700 Subject: [PATCH 12/40] Tune-Shark-V3 -- Small Bug Fix --- extensions/SharkPool/Tune-Shark-V3.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 063eb619ab..21b354d232 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.1.21 +// Version V.3.1.22 (function (Scratch) { "use strict"; @@ -807,7 +807,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. case "length": return this.modTime(src.buffer.duration, sound); case "current time": return !sound.context.playing ? 0 : this.calcTime( - sound.context.loop ? sound.loopParm[1] : src.buffer.duration, + sound.context.loop && sound.loopParm[1] ? sound.loopParm[1] : src.buffer.duration, sound.context.lastTimePlayed, src.context.currentTime, sound ); case "source": return sound.src; From 4974f874340af54adf0c4d8afade4bbec0ad6125 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 12 Jun 2024 20:44:45 -0700 Subject: [PATCH 13/40] Update Tune-Shark-V3.js --- extensions/SharkPool/Tune-Shark-V3.js | 56 +++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 21b354d232..b0baf0283a 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,8 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.1.22 +// Version V.3.1.3 +// Thanks to HOME for the song "Resonance" being used as the default audio link (function (Scratch) { "use strict"; @@ -65,7 +66,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. }); } }; - load(runtime.extensionStorage["SPtuneShark3"]); + if (!Scratch.extensions.isPenguinMod) load(runtime.extensionStorage["SPtuneShark3"]); class SPtuneShark3 { constructor() { @@ -124,7 +125,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. text: "import sound from URL [URL] named [NAME]", blockIconURI: settingsIconURI, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, @@ -254,6 +255,16 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } }, }, + { + opcode: "toggleReverse", + blockType: Scratch.BlockType.COMMAND, + text: "toggle sound [NAME] reverse mode [TYPE]", + blockIconURI: settingsIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } + }, + }, { opcode: "loopParams", blockType: Scratch.BlockType.COMMAND, @@ -483,7 +494,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. }, soundBools: { acceptReporters: true, - items: ["exists", "playing", "paused", "looped", "overlaped", "binded"] + items: ["exists", "playing", "paused", "looped", "overlaped", "reversed", "binded"] }, effectMenu: { acceptReporters: true, @@ -633,7 +644,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. engine.sourceNode = engine.getSourceNode(); soundBank[args.NAME] = { context: engine, name: args.NAME, src: args.URL, effects: {}, - loaded: true, vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, + loaded: true, reversed: false, vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, loopParm: [0, 0], overlap: false, overlays: [], isBind: false, binds: {} }; resolve(); @@ -755,6 +766,24 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. if (args.TYPE === "off") this.typeOverlay(sound, "stop"); } + toggleReverse(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + if (sound.reversed === (args.TYPE === "on")) return; + sound.reversed = args.TYPE === "on"; + this.typeOverlay(sound, "stop"); + const node = sound.context.sourceNode; + const reverseBuffer = (audioBuffer) => { + for (let i = 0; i < audioBuffer.numberOfChannels; i++) { + audioBuffer.getChannelData(i).reverse(); + } + return audioBuffer; + } + const bufferSource = node.context.createBufferSource(); + bufferSource.buffer = reverseBuffer(node.buffer); + bufferSource.connect(node.context.destination); + } + loopParams(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; @@ -794,6 +823,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. case "paused": return sound.context.paused; case "looped": return sound.context.loop; case "overlaped": return sound.overlap; + case "reversed": return sound.reversed; case "binded": return sound.isBind; default: return false; } @@ -996,12 +1026,24 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. save2Project(args) { settings.canSave = args.SAVE === "save"; + if (!Scratch.extensions.isPenguinMod) { + if (settings.canSave) { + const convertBank = JSON.parse(JSON.stringify(soundBank)); + Object.values(convertBank).forEach(item => delete item.context); + runtime.extensionStorage["SPtuneShark3"] = { bank : convertBank, settings }; + } else { runtime.extensionStorage["SPtuneShark3"] = undefined } + } + } + + // PenguinMod Storage + serialize() { if (settings.canSave) { const convertBank = JSON.parse(JSON.stringify(soundBank)); Object.values(convertBank).forEach(item => delete item.context); - runtime.extensionStorage["SPtuneShark3"] = { bank : convertBank, settings }; - } else { runtime.extensionStorage["SPtuneShark3"] = undefined } + return { SPtuneShark3 : { bank : convertBank, settings } } + } } + deserialize(data) { load(data.SPtuneShark3) } } Scratch.extensions.register(new SPtuneShark3()); From d6a2c1e96c765d73efc0309ea181d78c8c37c98b Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:06:49 -0700 Subject: [PATCH 14/40] Tune-Shark-V3 -- Set Attack to 0 --- extensions/SharkPool/Tune-Shark-V3.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index b0baf0283a..df40aa69ca 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.1.3 +// Version V.3.1.31 // Thanks to HOME for the song "Resonance" being used as the default audio link (function (Scratch) { @@ -642,6 +642,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. const engine = new Pizzicato.Sound(args.URL, () => { try { engine.sourceNode = engine.getSourceNode(); + engine.attack = 0; soundBank[args.NAME] = { context: engine, name: args.NAME, src: args.URL, effects: {}, loaded: true, reversed: false, vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, From 5e40ddb8932163760f455f20254e92532addf159 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 17 Jul 2024 20:28:47 -0700 Subject: [PATCH 15/40] Update Tune-Shark-V3.js --- extensions/SharkPool/Tune-Shark-V3.js | 69 +++++++++++++++++---------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index df40aa69ca..44f9b983ce 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,8 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.1.31 -// Thanks to HOME for the song "Resonance" being used as the default audio link +// Version V.3.2.0 (function (Scratch) { "use strict"; @@ -30,12 +29,11 @@ const startFlag = "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48ZGVmcz48c3R5bGU+LmNscy0xLC5jbHMtMntmaWxsOiM0Y2JmNTY7c3Ryb2tlOiM0NTk5M2Q7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO30uY2xzLTJ7c3Ryb2tlLXdpZHRoOjEuNXB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tZ3JlZW4tZmxhZzwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNLjc1LDJBNi40NCw2LjQ0LDAsMCwxLDguNDQsMmgwYTYuNDQsNi40NCwwLDAsMCw3LjY5LDBWMTIuNGE2LjQ0LDYuNDQsMCwwLDEtNy42OSwwaDBhNi40NCw2LjQ0LDAsMCwwLTcuNjksMCIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjAuNzUiIHkxPSIxNi43NSIgeDI9IjAuNzUiIHkyPSIwLjc1Ii8+PC9zdmc+"; - // Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) -- Modified File + // Modified Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) // uses MIT License const scriptElement = document.createElement("script"); scriptElement.textContent = -`!function(e){"use strict";function t(e,t){this.options={},e=e||this.options;var i={frequency:350,peak:1};this.inputNode=this.filterNode=a.context.createBiquadFilter(),this.filterNode.type=t,this.outputNode=s.context.createGain(),this.filterNode.connect(this.outputNode);for(var n in i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]}function i(){var e,t,i=a.context.sampleRate*this.time,n=s.context.createBuffer(2,i,a.context.sampleRate),o=n.getChannelData(0),r=n.getChannelData(1);for(t=0;i>t;t++)e=this.reverse?i-t:t,o[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay),r[t]=(2*Math.random()-1)*Math.pow(1-e/i,this.decay);this.reverbNode.buffer&&(this.inputNode.disconnect(this.reverbNode),this.reverbNode.disconnect(this.wetGainNode),this.reverbNode=s.context.createConvolver(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode)),this.reverbNode.buffer=n}function n(e){this.options={},e=e||this.options;var t={cutoff_frequency_low:100,cutoff_frequency_high:8e3,low_band_gain:1,mid_band_gain:1,high_band_gain:1,low_peak:1,mid_peak:1,high_peak:1};this.inputNode=a.context.createGain(),this.outputNode=a.context.createGain(),this.lowFilterNode=a.context.createBiquadFilter(),this.lowFilterNode.type="lowpass",this.inputNode.connect(this.lowFilterNode),this.lowGainNode=a.context.createGain(),this.lowFilterNode.connect(this.lowGainNode),this.midFilterNode=a.context.createBiquadFilter(),this.midFilterNode.type="bandpass",this.inputNode.connect(this.midFilterNode),this.midGainNode=a.context.createGain(),this.midFilterNode.connect(this.midGainNode),this.highFilterNode=a.context.createBiquadFilter(),this.highFilterNode.type="highpass",this.inputNode.connect(this.highFilterNode),this.highGainNode=a.context.createGain(),this.highFilterNode.connect(this.highGainNode),this.analyserNode=a.context.createAnalyser(),this.lowGainNode.connect(this.analyserNode),this.midGainNode.connect(this.analyserNode),this.highGainNode.connect(this.analyserNode),this.analyserNode.connect(this.outputNode),this.analyserNode.minDecibels=-90,this.analyserNode.maxDecibels=15,this.analyserNode.smoothingTimeConstant=.85,this.analyserNode.fftSize=256,this.options.cutoff_frequency_low=t.cutoff_frequency_low,this.options.cutoff_frequency_high=t.cutoff_frequency_high;for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]}function o(e){for(var t=a.context.sampleRate,i=new Float32Array(t),n=Math.PI/180,o=0;t>o;o++){var s=2*o/t-1;i[o]=(3+e)*s*20*n/(Math.PI+e*Math.abs(s))}return i}var s={},a=s,r="object"==typeof module&&module.exports,c="function"==typeof define&&define.amd;r?module.exports=s:c?define([],s):e.Pizzicato=e.Pz=s;var h=e.AudioContext||e.webkitAudioContext;if(!h)return void console.error("No AudioContext found in this environment. Please ensure your window or global object contains a working AudioContext constructor function.");s.context=new h;var u=s.context.createGain();u.connect(s.context.destination),s.Util={isString:function(e){return"[object String]"===toString.call(e)},isObject:function(e){return"[object Object]"===toString.call(e)},isFunction:function(e){return"[object Function]"===toString.call(e)},isNumber:function(e){return"[object Number]"===toString.call(e)&&e===+e},isArray:function(e){return"[object Array]"===toString.call(e)},isInRange:function(e,t,i){return a.Util.isNumber(e)&&a.Util.isNumber(t)&&a.Util.isNumber(i)?e>=t&&i>=e:!1},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof a.Sound},isEffect:function(e){for(var t in s.Effects)if(e instanceof s.Effects[t])return!0;return!1},normalize:function(e,t,i){return a.Util.isNumber(e)&&a.Util.isNumber(t)&&a.Util.isNumber(i)?(i-t)*e/1+t:void 0},getDryLevel:function(e){return!a.Util.isNumber(e)||e>1||0>e?0:.5>=e?1:1-2*(e-.5)},getWetLevel:function(e){return!a.Util.isNumber(e)||e>1||0>e?0:e>=.5?1:1-2*(.5-e)}};var d=s.context.createGain(),l=Object.getPrototypeOf(Object.getPrototypeOf(d)),f=l.connect;l.connect=function(e){var t=a.Util.isEffect(e)?e.inputNode:e;return f.call(this,t),e},Object.defineProperty(s,"volume",{enumerable:!0,get:function(){return u.gain.value},set:function(e){a.Util.isInRange(e,0,1)&&u&&(u.gain.value=e)}}),Object.defineProperty(s,"masterGainNode",{enumerable:!1,get:function(){return u},set:function(e){console.error("Can't set the master gain node")}}),s.Events={on:function(e,t,i){if(e&&t){this._events=this._events||{};var n=this._events[e]||(this._events[e]=[]);n.push({callback:t,context:i||this,handler:this})}},trigger:function(e){if(e){var t,i,n,o;if(this._events=this._events||{},t=this._events[e]||(this._events[e]=[])){for(i=Math.max(0,arguments.length-1),n=[],o=0;i>o;o++)n[o]=arguments[o+1];for(o=0;o1?(e.shift(),void o(e,t)):(i=i||new Error("Error decoding audio file "+e[0]),void(d.isFunction(t)&&t(i)))}.bind(u))},i.onreadystatechange=function(t){4===i.readyState&&200!==i.status&&console.error("Error while fetching "+e[0]+". "+i.statusText)},i.send()}function r(e,t){if(navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,!navigator.getUserMedia&&(!navigator.mediaDevices||navigator.mediaDevices.getUserMedia))return void console.error("Your browser does not support getUserMedia. Note that the current document must be loaded securely for this to work");var i=function(e){u.getRawSourceNode=function(){return s.context.createMediaStreamSource(e)},d.isFunction(t)&&t()}.bind(u),n=function(e){d.isFunction(t)&&t(e)};navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia({audio:!0}).then(i)["catch"](n):navigator.getUserMedia({audio:!0},i,n)}function c(e,t){var i=d.isFunction(e)?e:e.audioFunction,n=d.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!n)try{s.context.createScriptProcessor()}catch(o){n=2048}this.getRawSourceNode=function(){var e=s.context.createScriptProcessor(n,1,1);return e.onaudioprocess=i,e}}function h(e,t){this.getRawSourceNode=e.sound.getRawSourceNode,e.sound.sourceNode&&a.Util.isOscillator(e.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=e.sound.frequency)}var u=this,d=s.Util,l=i(e),f=d.isObject(e)&&d.isObject(e.options),p=.04,v=.04;if(l)throw console.error(l),new Error("Error initializing Pizzicato Sound: "+l);this.detached=f&&e.options.detached,this.masterVolume=s.context.createGain(),this.fadeNode=s.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(s.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=f&&e.options.loop,this.attack=f&&d.isNumber(e.options.attack)?e.options.attack:p,this.volume=f&&d.isNumber(e.options.volume)?e.options.volume:1,f&&d.isNumber(e.options.release)?this.release=e.options.release:f&&d.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=v,e?d.isString(e)?o.bind(this)(e,t):d.isFunction(e)?c.bind(this)(e,t):"file"===e.source?o.bind(this)(e.options.path,t):"wave"===e.source?n.bind(this)(e.options,t):"input"===e.source?r.bind(this)(e,t):"script"===e.source?c.bind(this)(e.options,t):"sound"===e.source&&h.bind(this)(e.options,t):n.bind(this)({},t)},s.Sound.prototype=Object.create(s.Events,{play:{enumerable:!0,value:function(e,t){this.playing||(a.Util.isNumber(t)||(t=this.offsetTime||0),a.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),a.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=s.context.currentTime-t,this.sourceNode.start(a.context.currentTime+e,t)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=a.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/a.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new s.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),t=0;t0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var i=a.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this sound."),this;var i=this.playing;i&&this.pause();var n=0===t?this.fadeNode:this.effectConnectors[t-1];n.disconnect();var o=this.effectConnectors[t];o.disconnect(),e.disconnect(o),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var s;return s=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],n.connect(s),i&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(a.context.currentTime),!this.attack)return void this.fadeNode.gain.setTargetAtTime(1,a.context.currentTime,.001);var e=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,t=this.attack;e||(t=(1-this.fadeNode.gain.value)*this.attack),this.fadeNode.gain.setTargetAtTime(1,a.context.currentTime,2*t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,i=function(){return a.Util.isFunction(t.stop)?t.stop(0):t.disconnect()};this.fadeNode.gain.value;if(this.fadeNode.gain.cancelScheduledValues(a.context.currentTime),!this.release)return this.fadeNode.gain.setTargetAtTime(0,a.context.currentTime,.001),void i();var n=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,o=this.release;n||(o=this.fadeNode.gain.value*this.release),this.fadeNode.gain.setTargetAtTime(1e-5,a.context.currentTime,o/5),window.setTimeout(function(){i()},1e3*o)}}}),s.Group=function(e){e=e||[],this.mergeGainNode=a.context.createGain(),this.masterVolume=a.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(a.masterGainNode);for(var t=0;t-1?void console.warn("The Pizzicato.Sound object was already added to this group"):e.detached?void console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together."):(e.disconnect(a.masterGainNode),e.connect(this.mergeGainNode),void this.sounds.push(e)):void console.error("You can only add Pizzicato.Sound objects")}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);return-1===t?void console.warn("Cannot remove a sound that is not part of this group."):(e.disconnect(this.mergeGainNode),e.connect(a.masterGainNode),void this.sounds.splice(t,1))}},volume:{enumerable:!0,get:function(){return this.masterVolume?this.masterVolume.gain.value:void 0},set:function(e){a.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var i=a.context.createGain();return this.effectConnectors.push(i),e.connect(i),i.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t=this.effects.indexOf(e);if(-1===t)return console.warn("Cannot remove effect that is not applied to this group."),this;var i=0===t?this.mergeGainNode:this.effectConnectors[t-1];i.disconnect();var n=this.effectConnectors[t];n.disconnect(),e.disconnect(n),this.effectConnectors.splice(t,1),this.effects.splice(t,1);var o;return o=t>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[t],i.connect(o),this}}}),s.Effects={};var p=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});s.Effects.Delay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.feedbackGainNode=s.context.createGain(),this.delayNode=s.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Delay.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),s.Effects.Compressor=function(e){this.options={},e=e||this.options;var t={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};this.inputNode=this.compressorNode=s.context.createDynamicsCompressor(),this.outputNode=s.context.createGain(),this.compressorNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Compressor.prototype=Object.create(p,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){s.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){s.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){s.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){s.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){s.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),s.Effects.LowPassFilter=function(e){t.call(this,e,"lowpass")},s.Effects.HighPassFilter=function(e){t.call(this,e,"highpass")};var v=Object.create(p,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){s.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){s.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});s.Effects.LowPassFilter.prototype=v,s.Effects.HighPassFilter.prototype=v,s.Effects.Distortion=function(e){this.options={},e=e||this.options;var t={gain:.5};this.waveShaperNode=s.context.createWaveShaper(),this.inputNode=this.outputNode=this.waveShaperNode;for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Distortion.prototype=Object.create(p,{gain:{enumerable:!0,get:function(){return this.options.gain},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.gain=e,this.adjustGain())}},adjustGain:{writable:!1,configurable:!1,enumerable:!1,value:function(){for(var e,t=a.Util.isNumber(this.options.gain)?parseInt(100*this.options.gain,10):50,i=44100,n=new Float32Array(i),o=Math.PI/180,s=0;i>s;++s)e=2*s/i-1,n[s]=(3+t)*e*20*o/(Math.PI+t*Math.abs(e));this.waveShaperNode.curve=n}}}),s.Effects.Flanger=function(e){this.options={},e=e||this.options;var t={time:.45,speed:.2,depth:.1,feedback:.1,mix:.5};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.inputFeedbackNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.delayNode=s.context.createDelay(),this.oscillatorNode=s.context.createOscillator(),this.gainNode=s.context.createGain(),this.feedbackNode=s.context.createGain(),this.oscillatorNode.type="sine",this.inputNode.connect(this.inputFeedbackNode),this.inputNode.connect(this.dryGainNode),this.inputFeedbackNode.connect(this.delayNode),this.inputFeedbackNode.connect(this.wetGainNode),this.delayNode.connect(this.wetGainNode),this.delayNode.connect(this.feedbackNode),this.feedbackNode.connect(this.inputFeedbackNode),this.oscillatorNode.connect(this.gainNode),this.gainNode.connect(this.delayNode.delayTime),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),this.oscillatorNode.start(0);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Flanger.prototype=Object.create(p,{time:{enumberable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.time=e,this.delayNode.delayTime.value=a.Util.normalize(e,.001,.02))}},speed:{enumberable:!0,get:function(){return this.options.speed},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.speed=e,this.oscillatorNode.frequency.value=a.Util.normalize(e,.5,5))}},depth:{enumberable:!0,get:function(){return this.options.depth},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.depth=e,this.gainNode.gain.value=a.Util.normalize(e,5e-4,.005))}},feedback:{enumberable:!0,get:function(){return this.options.feedback},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.feedback=e,this.feedbackNode.gain.value=a.Util.normalize(e,0,.8))}},mix:{enumberable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}}}),s.Effects.StereoPanner=function(e){this.options={},e=e||this.options;var t={pan:0};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),s.context.createStereoPanner?(this.pannerNode=s.context.createStereoPanner(),this.inputNode.connect(this.pannerNode),this.pannerNode.connect(this.outputNode)):s.context.createPanner?(console.warn("Your browser does not support the StereoPannerNode. Will use PannerNode instead."),this.pannerNode=s.context.createPanner(),this.pannerNode.type="equalpower",this.inputNode.connect(this.pannerNode),this.pannerNode.connect(this.outputNode)):(console.warn("Your browser does not support the Panner effect."),this.inputNode.connect(this.outputNode));for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.StereoPanner.prototype=Object.create(p,{pan:{enumerable:!0,get:function(){return this.options.pan},set:function(e){if(a.Util.isInRange(e,-1,1)&&(this.options.pan=e,this.pannerNode)){var t=this.pannerNode.toString().indexOf("StereoPannerNode")>-1;t?this.pannerNode.pan.value=e:this.pannerNode.setPosition(e,0,1-Math.abs(e))}}}}),s.Effects.Convolver=function(e,t){this.options={},e=e||this.options;var i=this,n=new XMLHttpRequest,o={mix:.5};this.callback=t,this.inputNode=s.context.createGain(),this.convolverNode=s.context.createConvolver(),this.outputNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var r in o)this[r]=e[r],this[r]=void 0===this[r]||null===this[r]?o[r]:this[r];return e.impulse?(n.open("GET",e.impulse,!0),n.responseType="arraybuffer",n.onload=function(e){var t=e.target.response;s.context.decodeAudioData(t,function(e){i.convolverNode.buffer=e,i.callback&&a.Util.isFunction(i.callback)&&i.callback()},function(e){e=e||new Error("Error decoding impulse file"),i.callback&&a.Util.isFunction(i.callback)&&i.callback(e)})},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e.impulse+". "+n.statusText)},void n.send()):void console.error("No impulse file specified.")},s.Effects.Convolver.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}}}),s.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var t={feedback:.5,time:.3,mix:.5};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.delayNodeLeft=s.context.createDelay(),this.delayNodeRight=s.context.createDelay(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.feedbackGainNode=s.context.createGain(),this.channelMerger=s.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.PingPongDelay.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),s.Effects.Reverb=function(e){this.options={},e=e||this.options;var t={mix:.5,time:.01,decay:.01,reverse:!1};this.inputNode=s.context.createGain(),this.reverbNode=s.context.createConvolver(),this.outputNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode);for(var n in t)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?t[n]:this[n];i.bind(this)()},s.Effects.Reverb.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,1e-4,10)&&(this.options.time=e,i.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){a.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,i.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){a.Util.isBool(e)&&(this.options.reverse=e,i.bind(this)())}}}),s.Effects.ThreeBandEqualizer=function(e){n.call(this,e)};var N=Object.create(p,{cutoff_frequency_low:{enumerable:!0,get:function(){return this.options.cutoff_frequency_low},set:function(e){s.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_low=e,this.lowFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},cutoff_frequency_high:{enumerable:!0,get:function(){return this.options.cutoff_frequency_high},set:function(e){s.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_high=e,this.highFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},low_band_gain:{enumerable:!0,get:function(){return this.options.low_band_gain},set:function(e){s.Util.isInRange(e,-40,15)&&(this.options.low_band_gain=e,this.lowGainNode.gain.value=Math.pow(10,e/20))}},mid_band_gain:{enumerable:!0,get:function(){return this.options.mid_band_gain},set:function(e){s.Util.isInRange(e,-40,15)&&(this.options.mid_band_gain=e,this.midGainNode.gain.value=Math.pow(10,e/20))}},high_band_gain:{enumerable:!0,get:function(){return this.options.high_band_gain},set:function(e){s.Util.isInRange(e,-40,15)&&(this.options.high_band_gain=e,this.highGainNode.gain.value=Math.pow(10,e/20))}},low_peak:{enumerable:!0,get:function(){return this.lowFilterNode.Q.value},set:function(e){s.Util.isInRange(e,1e-4,100)&&(this.lowFilterNode.Q.value=e)}},mid_peak:{enumerable:!0,get:function(){return this.midFilterNode.Q.value},set:function(e){s.Util.isInRange(e,1e-4,100)&&(this.midFilterNode.Q.value=e)}},high_peak:{enumerable:!0,get:function(){return this.highFilterNode.Q.value},set:function(e){s.Util.isInRange(e,1e-4,1e3)&&(this.highFilterNode.Q.value=e)}},visualizerBinCount:{enumerable:!0,get:function(){return this.analyserNode.frequencyBinCount},set:function(e){s.Util.isInRange(e,16,1024)&&(this.analyzerNode.fftSize=e)}},analyser:{enumerable:!0,get:function(){return this.analyserNode}},frequencyData:{enumerable:!0,get:function(){return void 0===this.byteFrequencyData&&(this.byteFrequencyData=new Uint8Array(this.analyserNode.frequencyBinCount.value)),this.analyserNode.getByteFrequencyData(this.FrequencyData),this.byteFrequencyData}}});s.Effects.ThreeBandEqualizer.prototype=N,s.Effects.Tremolo=function(e){this.options={},e=e||this.options;var t={speed:4,depth:1,mix:.8};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.tremoloGainNode=s.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=s.context.createOscillator(),this.shaperNode=s.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.Tremolo.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){ -a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){a.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),s.Effects.DubDelay=function(e){this.options={},e=e||this.options;var t={feedback:.6,time:.7,mix:.5,cutoff:700};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.feedbackGainNode=s.context.createGain(),this.delayNode=s.context.createDelay(),this.bqFilterNode=s.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]},s.Effects.DubDelay.prototype=Object.create(p,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=s.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){a.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){a.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){a.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),s.Effects.RingModulator=function(e){this.options={},e=e||this.options;var t={speed:30,distortion:1,mix:.5};this.inputNode=s.context.createGain(),this.outputNode=s.context.createGain(),this.dryGainNode=s.context.createGain(),this.wetGainNode=s.context.createGain(),this.vIn=s.context.createOscillator(),this.vIn.start(0),this.vInGain=s.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=s.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=s.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new g(s.context),this.vInDiode2=new g(s.context),this.vInInverter3=s.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=s.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new g(s.context),this.vcDiode4=new g(s.context),this.outGain=s.context.createGain(),this.outGain.gain.value=3,this.compressor=s.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode);for(var i in t)this[i]=e[i],this[i]=void 0===this[i]||null===this[i]?t[i]:this[i]};var g=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};return g.prototype.setDistortion=function(e){return this.h=e,this.setCurve()},g.prototype.setCurve=function(){var e,t,i,n,o,s,a,r;for(t=1024,o=new Float32Array(t),e=s=0,a=o.length;a>=0?a>s:s>a;e=a>=0?++s:--s)i=(e-t/2)/(t/2),i=Math.abs(i),n=i<=this.vb?0:this.vbi;i++)t[i].setDistortion(e)}}}}),s.Effects.Quadrafuzz=function(e){this.options={},e=e||this.options;var t={lowGain:.6,midLowGain:.8,midHighGain:.5,highGain:.6};this.inputNode=a.context.createGain(),this.outputNode=a.context.createGain(),this.dryGainNode=a.context.createGain(),this.wetGainNode=a.context.createGain(),this.lowpassLeft=a.context.createBiquadFilter(),this.lowpassLeft.type="lowpass",this.lowpassLeft.frequency.value=147,this.lowpassLeft.Q.value=.7071,this.bandpass1Left=a.context.createBiquadFilter(),this.bandpass1Left.type="bandpass",this.bandpass1Left.frequency.value=587,this.bandpass1Left.Q.value=.7071,this.bandpass2Left=a.context.createBiquadFilter(),this.bandpass2Left.type="bandpass",this.bandpass2Left.frequency.value=2490,this.bandpass2Left.Q.value=.7071,this.highpassLeft=a.context.createBiquadFilter(),this.highpassLeft.type="highpass",this.highpassLeft.frequency.value=4980,this.highpassLeft.Q.value=.7071,this.overdrives=[];for(var i=0;4>i;i++)this.overdrives[i]=a.context.createWaveShaper(),this.overdrives[i].curve=o();this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode);var n=[this.lowpassLeft,this.bandpass1Left,this.bandpass2Left,this.highpassLeft];for(i=0;i=t&&e<=n},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof i.Sound},isEffect:function(e){for(var i in t.Effects)if(e instanceof t.Effects[i])return!0;return!1},normalize:function(e,t,n){if(i.Util.isNumber(e)&&i.Util.isNumber(t)&&i.Util.isNumber(n))return(n-t)*e/1+t},getDryLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e<=.5?1:1-(e-.5)*2},getWetLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e>=.5?1:1-(.5-e)*2}};var r=Object.getPrototypeOf(Object.getPrototypeOf(t.context.createGain())),c=r.connect;r.connect=function(e){var t=i.Util.isEffect(e)?e.inputNode:e;return c.call(this,t),e},Object.defineProperty(t,"volume",{enumerable:!0,get:function(){return a.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&a&&(a.gain.value=e)}}),Object.defineProperty(t,"masterGainNode",{enumerable:!1,get:function(){return a},set:function(e){console.error("Can't set the master gain node")}}),t.Events={on:function(e,t,i){e&&t&&(this._events=this._events||{},(this._events[e]||(this._events[e]=[])).push({callback:t,context:i||this,handler:this}))},trigger:function(e){var t,i,n,o;if(e){for(this._events=this._events||{},t=this._events[e]||(this._events[e]=[]),o=0,i=Math.max(0,arguments.length-1),n=[];o1){e.shift(),h(e,i);return}t=t||Error("Error decoding audio file "+e[0]),s.isFunction(i)&&i(t)}).bind(o))},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e[0]+". "+n.statusText)},n.send()}function u(e,i){var n=s.isFunction(e)?e:e.audioFunction,o=s.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!o)try{t.context.createScriptProcessor()}catch(a){o=2048}this.getRawSourceNode=function(){var e=t.context.createScriptProcessor(o,1,1);return e.onaudioprocess=n,e}}this.detached=r&&e.options.detached,this.masterVolume=t.context.createGain(),this.fadeNode=t.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(t.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=r&&e.options.loop,this.attack=r&&s.isNumber(e.options.attack)?e.options.attack:.04,this.volume=r&&s.isNumber(e.options.volume)?e.options.volume:1,r&&s.isNumber(e.options.release)?this.release=e.options.release:r&&s.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=.04,e?s.isString(e)?h.bind(this)(e,n):s.isFunction(e)?u.bind(this)(e,n):"file"===e.source?h.bind(this)(e.options.path,n):"wave"===e.source?c.bind(this)(e.options,n):"input"===e.source?(function e(i,n){if(navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,!navigator.getUserMedia&&(!navigator.mediaDevices||navigator.mediaDevices.getUserMedia)){console.error("Your browser does not support getUserMedia. Note that the current document must be loaded securely for this to work");return}var a=(function(e){o.getRawSourceNode=function(){return t.context.createMediaStreamSource(e)},s.isFunction(n)&&n()}).bind(o),r=function(e){s.isFunction(n)&&n(e)};navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia({audio:!0}).then(a).catch(r):navigator.getUserMedia({audio:!0},a,r)}).bind(this)(e,n):"script"===e.source?u.bind(this)(e.options,n):"sound"===e.source&&(function e(t,n){this.getRawSourceNode=t.sound.getRawSourceNode,t.sound.sourceNode&&i.Util.isOscillator(t.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=t.sound.frequency)}).bind(this)(e.options,n):c.bind(this)({},n)},t.Sound.prototype=Object.create(t.Events,{play:{enumerable:!0,value:function(e,n){this.playing||(i.Util.isNumber(n)||(n=this.offsetTime||0),i.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),i.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=t.context.currentTime-n,this.sourceNode.start(i.context.currentTime+e,n)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=i.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/i.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new t.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),i=0;i0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this sound."),this;var n=this.playing;n&&this.pause();var o=0===i?this.fadeNode:this.effectConnectors[i-1];o.disconnect();var s=this.effectConnectors[i];return s.disconnect(),e.disconnect(s),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],o.connect(t),n&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.attack){this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,.001);return}var e=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,t=this.attack;e||(t=(1-this.fadeNode.gain.value)*this.attack),this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,2*t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,n=function(){return i.Util.isFunction(t.stop)?t.stop(0):t.disconnect()};if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.release){this.fadeNode.gain.setTargetAtTime(0,i.context.currentTime,.001),n();return}var o=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,s=this.release;o||(s=this.fadeNode.gain.value*this.release),this.fadeNode.gain.setTargetAtTime(1e-5,i.context.currentTime,s/5),window.setTimeout(function(){n()},1e3*s)}}}),t.Group=function(e){e=e||[],this.mergeGainNode=i.context.createGain(),this.masterVolume=i.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(i.masterGainNode);for(var t=0;t-1){console.warn("The Pizzicato.Sound object was already added to this group");return}if(e.detached){console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together.");return}e.disconnect(i.masterGainNode),e.connect(this.mergeGainNode),this.sounds.push(e)}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);if(-1===t){console.warn("Cannot remove a sound that is not part of this group.");return}e.disconnect(this.mergeGainNode),e.connect(i.masterGainNode),this.sounds.splice(t,1)}},volume:{enumerable:!0,get:function(){if(this.masterVolume)return this.masterVolume.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this group."),this;var n=0===i?this.mergeGainNode:this.effectConnectors[i-1];n.disconnect();var o=this.effectConnectors[i];return o.disconnect(),e.disconnect(o),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],n.connect(t),this}}}),t.Effects={};var h=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});function u(e,n){this.options={},e=e||this.options;var o={frequency:350,peak:1};for(var s in this.inputNode=this.filterNode=i.context.createBiquadFilter(),this.filterNode.type=n,this.outputNode=t.context.createGain(),this.filterNode.connect(this.outputNode),o)this[s]=e[s],this[s]=void 0===this[s]||null===this[s]?o[s]:this[s]}t.Effects.Delay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Delay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Compressor=function(e){this.options={},e=e||this.options;var i={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};for(var n in this.inputNode=this.compressorNode=t.context.createDynamicsCompressor(),this.outputNode=t.context.createGain(),this.compressorNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Compressor.prototype=Object.create(h,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){t.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){t.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){t.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),t.Effects.LowPassFilter=function(e){u.call(this,e,"lowpass")},t.Effects.HighPassFilter=function(e){u.call(this,e,"highpass")};var d=Object.create(h,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){t.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});function l(){var e,n,o=i.context.sampleRate*this.time,s=t.context.createBuffer(2,o,i.context.sampleRate),a=s.getChannelData(0),r=s.getChannelData(1);for(n=0;n-1?this.pannerNode.pan.value=e:this.pannerNode.setPosition(e,0,1-Math.abs(e))))}}}),t.Effects.Convolver=function(e,n){this.options={},e=e||this.options;var o=this,s=new XMLHttpRequest,a={mix:.5};for(var r in this.callback=n,this.inputNode=t.context.createGain(),this.convolverNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),a)this[r]=e[r],this[r]=void 0===this[r]||null===this[r]?a[r]:this[r];if(!e.impulse){console.error("No impulse file specified.");return}s.open("GET",e.impulse,!0),s.responseType="arraybuffer",s.onload=function(e){var n=e.target.response;t.context.decodeAudioData(n,function(e){o.convolverNode.buffer=e,o.callback&&i.Util.isFunction(o.callback)&&o.callback()},function(e){e=e||Error("Error decoding impulse file"),o.callback&&i.Util.isFunction(o.callback)&&o.callback(e)})},s.onreadystatechange=function(t){4===s.readyState&&200!==s.status&&console.error("Error while fetching "+e.impulse+". "+s.statusText)},s.send()},t.Effects.Convolver.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}}}),t.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.delayNodeLeft=t.context.createDelay(),this.delayNodeRight=t.context.createDelay(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.channelMerger=t.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.PingPongDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Reverb=function(e){this.options={},e=e||this.options;var i={mix:.5,time:.01,decay:.01,reverse:!1};for(var n in this.inputNode=t.context.createGain(),this.reverbNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n];l.bind(this)()},t.Effects.Reverb.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.time=e,l.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,l.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){i.Util.isBool(e)&&(this.options.reverse=e,l.bind(this)())}}}),t.Effects.Bitcrusher=function(e){this.inputNode=i.context.createGain(),this.outputNode=i.context.createGain(),this.bits=e.bits||4,this.frequency=e.frequency||44100,this.crusherNode=i.context.createScriptProcessor(4096,1,1);var t=this;this.crusherNode.onaudioprocess=function(e){for(var n=e.inputBuffer,o=e.outputBuffer,s=0;s=1&&e<=16&&(this.bits=e)},getBits:function(){return this.bits},setFrequency:function(e){e>0&&(this.frequency=e)},getFrequency:function(){return this.frequency}},t.Effects.ThreeBandEqualizer=function(e){f.call(this,e)};var p=Object.create(h,{cutoff_frequency_low:{enumerable:!0,get:function(){return this.options.cutoff_frequency_low},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_low=e,this.lowFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},cutoff_frequency_high:{enumerable:!0,get:function(){return this.options.cutoff_frequency_high},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_high=e,this.highFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},low_band_gain:{enumerable:!0,get:function(){return this.options.low_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.low_band_gain=e,this.lowGainNode.gain.value=Math.pow(10,e/20))}},mid_band_gain:{enumerable:!0,get:function(){return this.options.mid_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.mid_band_gain=e,this.midGainNode.gain.value=Math.pow(10,e/20))}},high_band_gain:{enumerable:!0,get:function(){return this.options.high_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.high_band_gain=e,this.highGainNode.gain.value=Math.pow(10,e/20))}},low_peak:{enumerable:!0,get:function(){return this.lowFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.lowFilterNode.Q.value=e)}},mid_peak:{enumerable:!0,get:function(){return this.midFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.midFilterNode.Q.value=e)}},high_peak:{enumerable:!0,get:function(){return this.highFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.highFilterNode.Q.value=e)}},visualizerBinCount:{enumerable:!0,get:function(){return this.analyserNode.frequencyBinCount},set:function(e){t.Util.isInRange(e,16,1024)&&(this.analyzerNode.fftSize=e)}},analyser:{enumerable:!0,get:function(){return this.analyserNode}},frequencyData:{enumerable:!0,get:function(){return void 0===this.byteFrequencyData&&(this.byteFrequencyData=new Uint8Array(this.analyserNode.frequencyBinCount.value)),this.analyserNode.getByteFrequencyData(this.FrequencyData),this.byteFrequencyData}}});t.Effects.ThreeBandEqualizer.prototype=p,t.Effects.Tremolo=function(e){this.options={},e=e||this.options;var i={speed:4,depth:1,mix:.8};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.tremoloGainNode=t.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=t.context.createOscillator(),this.shaperNode=t.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Tremolo.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){i.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),t.Effects.DubDelay=function(e){this.options={},e=e||this.options;var i={feedback:.6,time:.7,mix:.5,cutoff:700};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.bqFilterNode=t.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.DubDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){i.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),t.Effects.RingModulator=function(e){this.options={},e=e||this.options;var i={speed:30,distortion:1,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.vIn=t.context.createOscillator(),this.vIn.start(0),this.vInGain=t.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=t.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=t.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new N(t.context),this.vInDiode2=new N(t.context),this.vInInverter3=t.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=t.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new N(t.context),this.vcDiode4=new N(t.context),this.outGain=t.context.createGain(),this.outGain.gain.value=3,this.compressor=t.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]};var N=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};function v(e){for(var t=i.context.sampleRate,n=new Float32Array(t),o=Math.PI/180,s=0;sa;e=0<=a?++s:--s)n=(i=Math.abs(i=(e-t/2)/(t/2)))<=this.vb?0:this.vb { + runtime.on("SP_TUNE3_PROJECT_PAUSE", () => { if (runtime.ioDevices.clock._paused) Object.keys(soundBank).forEach(key => { soundBank[key].context.pause() }); else { Object.keys(soundBank).forEach(key => { @@ -125,7 +123,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. text: "import sound from URL [URL] named [NAME]", blockIconURI: settingsIconURI, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, @@ -420,6 +418,17 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } }, }, + { + opcode: "setBitcrush", + blockType: Scratch.BlockType.COMMAND, + text: "set bitcrush of sound [NAME] bits [BITS] freq [FREQ]", + blockIconURI: nobIconURI, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + BITS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 }, + FREQ: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60000 } + }, + }, { opcode: "setPass", blockType: Scratch.BlockType.COMMAND, @@ -489,7 +498,7 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. items: [ "length", "current time", "source", "binds", "volume", "pitch", "detune", "speed", "pan", "gain", "distortion", "reverb", "delay", "tremolo", "fuzz", - "highpass", "lowpass", "flanger", "compressor", "equalizer" + "bitcrush", "highpass", "lowpass", "flanger", "compressor", "equalizer" ] }, soundBools: { @@ -499,8 +508,9 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. effectMenu: { acceptReporters: true, items: [ - "all effects", "pitch", "detune", "speed", "pan", "gain", "distortion", "reverb", - "delay", "tremolo", "fuzz", "highpass", "lowpass", "flanger", "compressor", "equalizer" + "all effects", "pitch", "detune", "speed", "pan", "gain", "distortion", + "reverb", "delay", "tremolo", "fuzz", "bitcrush", "highpass", "lowpass", + "flanger", "compressor", "equalizer" ] } }, @@ -534,7 +544,8 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. sound.context.addEffect(effect); sound.effects[name] = effect; } else { - // Dont Remove the Effect (Causes Glitches in Forever Loops), simply reset each value + // Dont Remove the Effect (Causes Glitches in Forever Loops), simply change each value + // We use args just in case some Effects dont store them in the audio context const options = effect.options; const thisEffect = sound.context.effects.find(effect => effect.id === name); thisEffect.arguments = effect.arguments; @@ -546,9 +557,13 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. return; } case "DISTORTION": { return thisEffect.gain = options.gain } + case "BITCRUSH": { + thisEffect.frequency = Math.max(30000, Scratch.Cast.toNumber(args.FREQ)); + thisEffect.bits = Math.max(10, Scratch.Cast.toNumber(args.BITS)) / 10; + return; + } case "LOWPASS": case "HIGHPASS": { - // These Options Dont Save for Some Reason :/ const freq = Scratch.Cast.toNumber(args.FREQ); const peak = Scratch.Cast.toNumber(args.PEAK) / 5; thisEffect.filterNode.frequency.value = freq; @@ -560,7 +575,6 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. return; } case "COMPRESSOR": { - // These Options Dont Save for Some Reason :/ const node = thisEffect.compressorNode; const values = { threshold: Math.min(0, Math.max(-100, Scratch.Cast.toNumber(args.THRESH) * -1)), @@ -963,6 +977,21 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. this.updateEffect(fuzz, sound, "FUZZ", args); } + setBitcrush(args) { + const sound = soundBank[args.NAME]; + if (sound === undefined) return; + /* NOTE: Bitcrusher uses "ScriptProcessorNode" wich is deprecated. + From what Ive tested and read online, ending support for this doesnt seem to be + going anywhere. If a problem emerges we need to try and find a way to + use "AudioWorkletNodes" instead which is more complicated to use. + */ + const bitcrush = new Pizzicato.Effects.Bitcrusher({ + bits: Math.max(10, Scratch.Cast.toNumber(args.BITS)) / 10, + frequency : Math.max(30000, Scratch.Cast.toNumber(args.FREQ)) + }); + this.updateEffect(bitcrush, sound, "BITCRUSH", args); + } + setTremolo(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; @@ -1035,16 +1064,6 @@ a.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=s.Util. } else { runtime.extensionStorage["SPtuneShark3"] = undefined } } } - - // PenguinMod Storage - serialize() { - if (settings.canSave) { - const convertBank = JSON.parse(JSON.stringify(soundBank)); - Object.values(convertBank).forEach(item => delete item.context); - return { SPtuneShark3 : { bank : convertBank, settings } } - } - } - deserialize(data) { load(data.SPtuneShark3) } } Scratch.extensions.register(new SPtuneShark3()); From 7667d709a00835482651c76f7872e612637ca4bc Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:58:48 -0700 Subject: [PATCH 16/40] Tune-Shark-V3 -- V3.3 New Audio Effects -Attack -Release New Stuff -BPM Detector -Tone Detector Fixes -Highpass filter now sets properly --- extensions/SharkPool/Tune-Shark-V3.js | 124 ++++++++++++++++++-------- 1 file changed, 88 insertions(+), 36 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 44f9b983ce..43bc0964fb 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,8 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.2.0 +// Version V.3.3.0 +// Thanks to HOME for the song "Resonance" being used as the default audio link (function (Scratch) { "use strict"; @@ -15,19 +16,19 @@ const scratchAudio = runtime.audioEngine; const menuIconURI = -"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMDIuMTg1MTgiIGhlaWdodD0iMTAyLjE4NTE4IiB2aWV3Qm94PSIwLDAsMTAyLjE4NTE4LDEwMi4xODUxOCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE4OC45MDc0MSwtMTI4LjkwNzQxKSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTg4LjkwNzQxLDE4MGMwLC0yOC4yMTc2NiAyMi44NzQ5MywtNTEuMDkyNTkgNTEuMDkyNTksLTUxLjA5MjU5YzI4LjIxNzY2LDAgNTEuMDkyNTksMjIuODc0OTMgNTEuMDkyNTksNTEuMDkyNTljMCwyOC4yMTc2NiAtMjIuODc0OTMsNTEuMDkyNTkgLTUxLjA5MjU5LDUxLjA5MjU5Yy0yOC4yMTc2NiwwIC01MS4wOTI1OSwtMjIuODc0OTMgLTUxLjA5MjU5LC01MS4wOTI1OXoiIGZpbGw9IiM0MDQwNDAiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTE5My43Njc0NCwxODBjMCwtMjUuNTMzNTQgMjAuNjk5MDIsLTQ2LjIzMjU2IDQ2LjIzMjU2LC00Ni4yMzI1NmMyNS41MzM1NCwwIDQ2LjIzMjU2LDIwLjY5OTAyIDQ2LjIzMjU2LDQ2LjIzMjU2YzAsMjUuNTMzNTQgLTIwLjY5OTAyLDQ2LjIzMjU2IC00Ni4yMzI1Niw0Ni4yMzI1NmMtMjUuNTMzNTQsMCAtNDYuMjMyNTYsLTIwLjY5OTAyIC00Ni4yMzI1NiwtNDYuMjMyNTZ6IiBmaWxsPSIjNjY2NjY2IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjxwYXRoIGQ9Ik0yNjEuNzQzNjIsMjE1LjU0OTY4bC0wLjAwMDQ2LC0wLjAwMjJjLTEuMjUyOTUsMi41MzUzOSAtNC42NjcsMy45MzQxMiAtOC4yNjMsMy4xOTY5OWMtNC4xMTgxNiwtMC44NDQ1NSAtNi45MTQ5NCwtNC4xNzAzNiAtNi4yNDY0NiwtNy40Mjg3OGMwLjY2ODI1LC0zLjI1OTA0IDQuNTQ4MDQsLTUuMjE1MzYgOC42NjYyMiwtNC4zNzA4MWMxLjg3NTMyLDAuMzg0MzggMy40NzQ0NSwxLjI4Mzk1IDQuNTk5MjYsMi40NTY2M2w2LjY4MzUzLC0xNC4xNzcwNGMtMTAuNTY1MzEsLTQuMTM1MDUgLTE5Ljc2MDIsLTUuMDk2NjQgLTE5Ljc2MDIsLTUuMDk2NjRsLTguOTg2MTEsMTkuMDYxMjRjLTAuOTY2NjUsMi45MjY5MiAtNC42MzY2MSw0LjYyMTY4IC04LjUxOTY3LDMuODI1ODJjLTQuMTE3NzUsLTAuODQ0MzYgLTYuOTE0MzMsLTQuMTcwNTcgLTYuMjQ2MjcsLTcuNDI5MTljMC42NjgwNiwtMy4yNTg2MyA0LjU0Nzg1LC01LjIxNDk2IDguNjY2MjIsLTQuMzcwODFjMS45MTAyOSwwLjM5MTQyIDMuNTM2MTksMS4zMTc1MyA0LjY2NDE1LDIuNTIyNTNsMTIuMDM0OCwtMjUuNTA3MDJjMCwwIDEzLjUzMjg1LDAuMjM1NiAyNi45NDc4Niw3LjExMzc5eiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTIxNy4yMTYyNSwxNTkuNzM4MzJsNC4wNDI1NiwyMy42ODM0NyIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIvPjxwYXRoIGQ9Ik0yMjIuOTM5OTYsMTg0LjEyNTU1YzEuMzgwOTIsMi42MDY0NiAtMC43NzE4NCw2LjA0MDQ2IC00LjgwODMzLDcuNjcwMDVjLTQuMDM2NDksMS42Mjk1OSAtOC40MjgxNywwLjgzNzY2IC05LjgwOTEsLTEuNzY4OGMtMS4zODA5MiwtMi42MDY0NiAwLjc3MTg0LC02LjA0MDQ2IDQuODA4MzMsLTcuNjcwMDVjNC4wMzY0OSwtMS42Mjk1OSA4LjQyODE3LC0wLjgzNzY2IDkuODA5MSwxLjc2ODh6IiBmaWxsPSIjZmZmZmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTIxNC45ODk2MywxNTcuMDU4NGMwLDAgOS43MDU4LC0xLjcwNDcyIDEyLjM2MjkxLDIuNzY5OTdjMi4zNzUxMiw0LjAwMDA4IDAuMDcxNTUsOC42OTQxOCAwLjIxMjA5LDEwLjEyNzMxYzAuMTQwNTQsMS40MzMxMyAyLjE3NTAzLDEuMTA4OTYgMi4xNzUwMywxLjEwODk2bC0wLjAxMzI2LDEuNjc0MTFjMCwwIC0yLjg2NDQ3LDAuNjY5MDIgLTMuNDExOTksLTEuMzE5MTVjLTAuNTQ3NTMsLTEuOTg4MTYgLTAuNTU5NTIsLTUuOTc4MjkgLTIuODIzNTQsLTguMjI3NzdjLTIuMjY0NjYsLTIuMjQ5MjcgLTcuNTM2ODcsLTAuNzU4NjkgLTcuNTM2ODcsLTAuNzU4NjkiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTI0OS4zNDMxMywxNjQuMjc4MDdjLTAuMzMzNTEsMi4xMTc2MSAtMi4zNTI4NywzLjMwODgxIC00LjE4OTA2LDQuMDAxNThjLTEuNjQ5NzQsMC43MjYxIC0zLjU0OTg5LDEuMDA4MDIgLTUuMjc1NjUsMC4zNjk5Yy0xLjYzMTksLTAuMzgwOTkgLTMuMTg4MjEsLTEuODA2NzYgLTIuOTk0NiwtMy41OTczM2MwLjE1NDQzLC0yLjA4NTA5IDEuODk2NTksLTMuODIyNDMgMy44NDIzOCwtNC40MDQ5OGMxLjkzOTE1LC0wLjc5OTIyIDQuMjUyODcsLTAuNzMwNDYgNi4xMDQ2MSwwLjI1ODgzYzAuOTEyNywwLjQyMjE4IDIuMTg5MDgsLTE0LjE0MDYzIDMuMDM1MDksLTIwLjU4MTk4YzAuMTAwNDksLTAuNjQxOCAyLjU4Mzk4LC0wLjQ3MDkzIDIuNTEyOTMsMC4xNTEwNWMwLDAgLTEuODUzNTQsMTUuNTIyMyAtMy4wMzU3MSwyMy44MDI5MnoiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvZz48L2c+PC9zdmc+"; +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDIuMTg1IiBoZWlnaHQ9IjEwMi4xODUiIHZpZXdCb3g9IjAgMCAxMDIuMTg1IDEwMi4xODUiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgNTEuMDkzQzAgMjIuODc1IDIyLjg3NSAwIDUxLjA5MyAwczUxLjA5MyAyMi44NzUgNTEuMDkzIDUxLjA5My0yMi44NzUgNTEuMDkzLTUxLjA5MyA1MS4wOTNTMCA3OS4zMTEgMCA1MS4wOTMiIGZpbGw9IiM0MDQwNDAiLz48cGF0aCBkPSJNNC44NiA1MS4wOTNjMC0yNS41MzQgMjAuNy00Ni4yMzMgNDYuMjMzLTQ2LjIzMyAyNS41MzQgMCA0Ni4yMzMgMjAuNyA0Ni4yMzMgNDYuMjMzIDAgMjUuNTM0LTIwLjcgNDYuMjMzLTQ2LjIzMyA0Ni4yMzMtMjUuNTM0IDAtNDYuMjMzLTIwLjctNDYuMjMzLTQ2LjIzMyIgZmlsbD0iIzY2NiIvPjxwYXRoIGQ9Ik03Mi44MzcgODYuNjQzdi0uMDAzYy0xLjI1NCAyLjUzNi00LjY2OCAzLjkzNS04LjI2NCAzLjE5Ny00LjExOC0uODQ0LTYuOTE1LTQuMTctNi4yNDYtNy40MjguNjY4LTMuMjYgNC41NDgtNS4yMTYgOC42NjYtNC4zNzEgMS44NzUuMzg0IDMuNDc0IDEuMjg0IDQuNiAyLjQ1N2w2LjY4My0xNC4xNzhjLTEwLjU2Ni00LjEzNS0xOS43Ni01LjA5Ni0xOS43Ni01LjA5NmwtOC45ODcgMTkuMDYxYy0uOTY2IDIuOTI3LTQuNjM2IDQuNjIyLTguNTIgMy44MjYtNC4xMTctLjg0NC02LjkxNC00LjE3LTYuMjQ2LTcuNDMuNjY5LTMuMjU4IDQuNTQ4LTUuMjE0IDguNjY3LTQuMzcgMS45MS4zOTEgMy41MzYgMS4zMTcgNC42NjQgMi41MjJsMTIuMDM1LTI1LjUwN3MxMy41MzIuMjM2IDI2Ljk0NyA3LjExNHoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtMjguMzA5IDMwLjgzMSA0LjA0MyAyMy42ODQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIi8+PHBhdGggZD0iTTM0LjAzMyA1NS4yMTljMS4zOCAyLjYwNi0uNzcyIDYuMDQtNC44MDggNy42Ny00LjAzNyAxLjYzLTguNDI5LjgzNy05LjgxLTEuNzctMS4zOC0yLjYwNi43NzItNi4wNCA0LjgwOS03LjY3IDQuMDM2LTEuNjI5IDguNDI4LS44MzcgOS44MDkgMS43N20tNy45NS0yNy4wNjhzOS43MDUtMS43MDQgMTIuMzYzIDIuNzdjMi4zNzUgNCAuMDcxIDguNjk1LjIxMiAxMC4xMjguMTQgMS40MzMgMi4xNzUgMS4xMDkgMi4xNzUgMS4xMDlsLS4wMTQgMS42NzRzLTIuODY0LjY2OS0zLjQxMi0xLjMyYy0uNTQ3LTEuOTg4LS41Ni01Ljk3OC0yLjgyMy04LjIyNy0yLjI2NS0yLjI1LTcuNTM3LS43NTktNy41MzctLjc1OSIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik02MC40MzYgMzUuMzcxYy0uMzMzIDIuMTE4LTIuMzUzIDMuMzA5LTQuMTg5IDQuMDAyLTEuNjUuNzI2LTMuNTUgMS4wMDgtNS4yNzYuMzctMS42MzEtLjM4MS0zLjE4OC0xLjgwNy0yLjk5NC0zLjU5OC4xNTQtMi4wODUgMS44OTYtMy44MjIgMy44NDItNC40MDUgMS45NC0uNzk5IDQuMjUzLS43MyA2LjEwNS4yNi45MTMuNDIxIDIuMTg5LTE0LjE0MiAzLjAzNS0yMC41ODMuMS0uNjQyIDIuNTg0LS40NyAyLjUxMy4xNTEgMCAwLTEuODU0IDE1LjUyMi0zLjAzNiAyMy44MDMiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4="; const blockIconURI = -"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMjk5LC0xNDAuNTEyOTkpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTIwMC41MTMsMjE5LjQ4NzAxdi03OC45NzQwMWg3OC45NzQwMXY3OC45NzQwMXoiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIvPjxwYXRoIGQ9Ik0yNjEuNzE1MiwxOTQuMzQ4NjZsLTAuMDAxNzcsLTAuMDAyMzZjLTAuMDY4NDQsMy43MDkzNSAtMy4zMzcwNiw3LjI3ODg3IC04LjAxNjM4LDguNDE1ODFjLTUuMzU5LDEuMzAxNTUgLTEwLjUzODA1LC0xLjA4MDMgLTExLjU2NzYxLC01LjMyMDY1Yy0xLjAzMDE1LC00LjI0MDk1IDIuNDc5MTksLTguNzMyNjQgNy44MzgyLC0xMC4wMzQxOWMyLjQ0MDI1LC0wLjU5Mjk1IDQuODQwOTgsLTAuNDIwMDggNi44MzE2NSwwLjM0MjJ2LTIwLjU2MTAzYy0xNC44NDk4LDEuMDAzNiAtMjYuMjk4MjMsNS4wMDYxOCAtMjYuMjk4MjMsNS4wMDYxOHYyNy42NDQ2MmMwLjQ5MDI5LDQuMDEzNzkgLTIuOTE2MzksOC4wNzc3NCAtNy45NjkxOCw5LjMwNTU0Yy01LjM1ODQxLDEuMzAxNTUgLTEwLjUzNzQ2LC0xLjA4MDg5IC0xMS41Njc2MSwtNS4zMjEyNGMtMS4wMzAxNSwtNC4yNDAzNiAyLjQ3OTE5LC04LjczMjA1IDcuODM4MiwtMTAuMDM0MTljMi40ODU2OCwtMC42MDQxNiA0LjkzMzAyLC0wLjQxNDc3IDYuOTQ1NTIsMC4zODQwOWwwLjAxMTgsLTM2Ljk5ODUzYzAsMCAxNi4xODk3LC03LjI5MDY3IDM1Ljk1NTQxLC02LjYzMzQxeiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+"; +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIj48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Im02MS4yMDIgNTMuODM2LS4wMDItLjAwM2MtLjA2OCAzLjcxLTMuMzM3IDcuMjgtOC4wMTYgOC40MTYtNS4zNTkgMS4zMDItMTAuNTM4LTEuMDgtMTEuNTY4LTUuMzJzMi40OC04LjczMyA3LjgzOS0xMC4wMzVjMi40NC0uNTkzIDQuODQtLjQyIDYuODMxLjM0MnYtMjAuNTZjLTE0Ljg1IDEuMDAzLTI2LjI5OCA1LjAwNi0yNi4yOTggNS4wMDZ2MjcuNjQ0Yy40OSA0LjAxNC0yLjkxNiA4LjA3OC03Ljk3IDkuMzA2LTUuMzU4IDEuMzAxLTEwLjUzNy0xLjA4MS0xMS41NjctNS4zMjFzMi40OC04LjczMyA3LjgzOC0xMC4wMzVjMi40ODYtLjYwNCA0LjkzMy0uNDE0IDYuOTQ2LjM4NGwuMDEyLTM2Ljk5OHMxNi4xOS03LjI5IDM1Ljk1NS02LjYzM3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; const settingsIconURI = -"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMjk5LC0xNDAuNTEyOTgpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjY1LjA0NTY5LDE4My4xMjI3MWwyLjA0MiwwLjg1NTIxYzIuNTU3MTgsMS4wNzA5NyAzLjc2MTk4LDQuMDEyMTggMi42OTEwMSw2LjU2OTM2bC0xLjM3MTM0LDMuMjc0MzhjLTEuMDcwOTcsMi41NTcxOCAtNC4wMTIxOCwzLjc2MiAtNi41NjkzNiwyLjY5MTAzbC0yLjA0MiwtMC44NTUyMWMtMS4yNjc5NiwxLjYwNDkgLTIuNzEzMzcsMy4wMzAxMiAtNC4yOTQ4Nyw0LjI1ODcxbDAuODM5OTUsMi4wNTA1MmMxLjA1MDkxLDIuNTY1NDkgLTAuMTc2OSw1LjQ5NzE2IC0yLjc0MjQsNi41NDgwN2wtMy4yODUwMywxLjM0NTY1Yy0yLjU2NTQ5LDEuMDUwOTEgLTUuNDk3MTYsLTAuMTc2OSAtNi41NDgwNywtMi43NDIzOWwtMC44Mzk5NiwtMi4wNTA1MmMtMS45ODg5MiwwLjIzMzk4IC00LjAxODgxLDAuMjMyMzYgLTYuMDQ4MzEsLTAuMDIxODFsLTAuODU1MiwyLjA0MTk5Yy0xLjA3MDk3LDIuNTU3MTggLTQuMDEyMTgsMy43NjE5OCAtNi41NjkzNiwyLjY5MTAxbC0zLjI3NDM4LC0xLjM3MTM0Yy0yLjU1NzE4LC0xLjA3MDk3IC0zLjc2MiwtNC4wMTIxNyAtMi42OTEwMywtNi41NjkzNWwwLjg1NTIxLC0yLjA0MTk5Yy0xLjYwNDksLTEuMjY3OTYgLTMuMDMwMTMsLTIuNzEzMzcgLTQuMjU4NzEsLTQuMjk0ODdsLTIuMDUwNTIsMC44Mzk5NWMtMi41NjU0OSwxLjA1MDkxIC01LjQ5NzE2LC0wLjE3NjkgLTYuNTQ4MDcsLTIuNzQyMzlsLTEuMzQ1NjYsLTMuMjg1MDNjLTEuMDUwOTEsLTIuNTY1NDkgMC4xNzY5LC01LjQ5NzE3IDIuNzQyNCwtNi41NDgwOGwyLjA1MDUyLC0wLjgzOTk2Yy0wLjIzMzk3LC0xLjk4ODkyIC0wLjIzMjM2LC00LjAxODggMC4wMjE4MSwtNi4wNDgzbC0yLjA0MiwtMC44NTUyMWMtMi41NTcxOCwtMS4wNzA5NyAtMy43NjE5OCwtNC4wMTIxOCAtMi42OTEwMSwtNi41NjkzNmwxLjM3MTM1LC0zLjI3NDM4YzEuMDcwOTcsLTIuNTU3MTggNC4wMTIxNywtMy43NjIgNi41NjkzNSwtMi42OTEwM2wyLjA0MiwwLjg1NTIxYzEuMjY3OTYsLTEuNjA0OSAyLjcxMzM4LC0zLjAzMDEyIDQuMjk0ODgsLTQuMjU4NzFsLTAuODM5OTYsLTIuMDUwNTJjLTEuMDUwOTEsLTIuNTY1NDkgMC4xNzY5LC01LjQ5NzE2IDIuNzQyNCwtNi41NDgwN2wzLjI4NTAzLC0xLjM0NTY1YzIuNTY1NDksLTEuMDUwOTEgNS40OTcxNiwwLjE3NjkgNi41NDgwNywyLjc0MjM5bDAuODM5OTYsMi4wNTA1MmMxLjk4ODkyLC0wLjIzMzk3IDQuMDE4ODEsLTAuMjMyMzYgNi4wNDgzMSwwLjAyMTgxbDAuODU1MjEsLTIuMDQyYzEuMDcwOTcsLTIuNTU3MTggNC4wMTIxOCwtMy43NjE5OCA2LjU2OTM2LC0yLjY5MTAxbDMuMjc0MzgsMS4zNzEzNGMyLjU1NzE4LDEuMDcwOTcgMy43NjIsNC4wMTIxNyAyLjY5MTAzLDYuNTY5MzVsLTAuODU1MjEsMi4wNDJjMS42MDQ5LDEuMjY3OTYgMy4wMzAxMywyLjcxMzM3IDQuMjU4NzEsNC4yOTQ4N2wyLjA1MDUyLC0wLjgzOTk1YzIuNTY1NDksLTEuMDUwOTEgNS40OTcxNywwLjE3NjkgNi41NDgwOCwyLjc0MjM5bDEuMzQ1NjUsMy4yODUwM2MxLjA1MDkxLDIuNTY1NDkgLTAuMTc2OSw1LjQ5NzE2IC0yLjc0MjQsNi41NDgwN2wtMi4wNTA1MiwwLjgzOTk2YzAuMjMzOTcsMS45ODg5MiAwLjIzMjM2LDQuMDE4OCAtMC4wMjE4MSw2LjA0ODN6TTIyNy41NDgyMiwxNzQuNzg1MDdjLTIuODgwMTMsNi44NzY5NSAwLjM1OTkyLDE0Ljc4NjYyIDcuMjM2ODYsMTcuNjY2NzVjNi44NzY5NSwyLjg4MDEzIDE0Ljc4NjYyLC0wLjM1OTkyIDE3LjY2Njc1LC03LjIzNjg2YzIuODgwMTMsLTYuODc2OTUgLTAuMzU5OTIsLTE0Ljc4NjYyIC03LjIzNjg2LC0xNy42NjY3NWMtNi44NzY5NSwtMi44ODAxMyAtMTQuNzg2NjIsMC4zNTk5MiAtMTcuNjY2NzUsNy4yMzY4NnoiIGZpbGw9IiNmZmZmZmYiLz48cGF0aCBkPSJNMjAwLjUxMywyMTkuNDg3di03OC45NzQwMWg3OC45NzQwMXY3OC45NzQwMXoiIGZpbGw9Im5vbmUiLz48L2c+PC9nPjwvc3ZnPg=="; +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiID48cGF0aCBkPSJtNjQuNTMzIDQyLjYxIDIuMDQyLjg1NWE1LjAyIDUuMDIgMCAwIDEgMi42OSA2LjU3bC0xLjM3IDMuMjc0YTUuMDIgNS4wMiAwIDAgMS02LjU3IDIuNjlsLTIuMDQyLS44NTVhMjUgMjUgMCAwIDEtNC4yOTUgNC4yNmwuODQgMi4wNWE1LjAyIDUuMDIgMCAwIDEtMi43NDIgNi41NDhsLTMuMjg1IDEuMzQ1YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtLjg0LTIuMDVhMjUgMjUgMCAwIDEtNi4wNDktLjAyMmwtLjg1NSAyLjA0MmE1LjAyIDUuMDIgMCAwIDEtNi41NyAyLjY5bC0zLjI3NC0xLjM3YTUuMDIgNS4wMiAwIDAgMS0yLjY5LTYuNTdsLjg1NS0yLjA0MmEyNSAyNSAwIDAgMS00LjI2LTQuMjk1bC0yLjA1Ljg0YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtMS4zNDUtMy4yODVhNS4wMiA1LjAyIDAgMCAxIDIuNzQyLTYuNTQ4bDIuMDUtLjg0YTI1IDI1IDAgMCAxIC4wMjItNi4wNDlsLTIuMDQyLS44NTVhNS4wMiA1LjAyIDAgMCAxLTIuNjktNi41N2wxLjM3LTMuMjc0YTUuMDIgNS4wMiAwIDAgMSA2LjU3LTIuNjlsMi4wNDIuODU1YTI1IDI1IDAgMCAxIDQuMjk1LTQuMjZsLS44NC0yLjA1YTUuMDIgNS4wMiAwIDAgMSAyLjc0Mi02LjU0OGwzLjI4NS0xLjM0NWE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsLjg0IDIuMDVhMjUgMjUgMCAwIDEgNi4wNDkuMDIybC44NTUtMi4wNDJhNS4wMiA1LjAyIDAgMCAxIDYuNTctMi42OWwzLjI3NCAxLjM3YTUuMDIgNS4wMiAwIDAgMSAyLjY5IDYuNTdsLS44NTUgMi4wNDJhMjUgMjUgMCAwIDEgNC4yNiA0LjI5NWwyLjA1LS44NGE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsMS4zNDUgMy4yODVhNS4wMiA1LjAyIDAgMCAxLTIuNzQyIDYuNTQ4bC0yLjA1Ljg0YTI1IDI1IDAgMCAxLS4wMjIgNi4wNDltLTM3LjQ5OC04LjMzOGMtMi44OCA2Ljg3Ny4zNiAxNC43ODcgNy4yMzcgMTcuNjY3czE0Ljc4Ny0uMzYgMTcuNjY3LTcuMjM3LS4zNi0xNC43ODctNy4yMzctMTcuNjY3LTE0Ljc4Ny4zNi0xNy42NjcgNy4yMzciIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjwvZz48L3N2Zz4="; const nobIconURI = -"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OC45NzQwMSIgaGVpZ2h0PSI3OC45NzQwMSIgdmlld0JveD0iMCwwLDc4Ljk3NDAxLDc4Ljk3NDAxIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAwLjUxMjk5LC0xNDAuNTEyOTkpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjAwLjUxMywyMTkuNDg3MDF2LTc4Ljk3NDAxaDc4Ljk3NDAxdjc4Ljk3NDAxeiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0yMzguNDc0MjEsMTUxLjM2MDY3Yy0xLjE2MjMxLDAgLTIuMTA0NTQsLTAuOTQyMjQgLTIuMTA0NTQsLTIuMTA0NTR2LTMuMDUxNTljMCwtMS4xNjIzMSAwLjk0MjI0LC0yLjEwNDU1IDIuMTA0NTQsLTIuMTA0NTVoMy4wNTE1OWMxLjE2MjMxLDAgMi4xMDQ1NCwwLjk0MjI0IDIuMTA0NTQsMi4xMDQ1NXYzLjA1MTU5YzAsMS4xNjIzMSAtMC45NDIyNCwyLjEwNDU0IC0yLjEwNDU0LDIuMTA0NTR6IiBmaWxsPSIjZmZmZmZmIi8+PHBhdGggZD0iTTIzOC40NzQyMSwyMTUuOWMtMS4xNjIzMSwwIC0yLjEwNDU0LC0wLjk0MjI0IC0yLjEwNDU0LC0yLjEwNDU0di0zLjA1MTU5YzAsLTEuMTYyMzEgMC45NDIyNCwtMi4xMDQ1NCAyLjEwNDU0LC0yLjEwNDU0aDMuMDUxNTljMS4xNjIzMSwwIDIuMTA0NTQsMC45NDIyMyAyLjEwNDU0LDIuMTA0NTR2My4wNTE1OWMwLDEuMTYyMzEgLTAuOTQyMjQsMi4xMDQ1NCAtMi4xMDQ1NCwyLjEwNDU0eiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yNTkuMTcyMTYsMTU4LjY3MDA0Yy0wLjgyMTg3LC0wLjgyMTg4IC0wLjgyMTg3LC0yLjE1NDM5IDAsLTIuOTc2MjdsMi4xNTc4LC0yLjE1NzhjMC44MjE4NywtMC44MjE4OCAyLjE1NDM5LC0wLjgyMTg4IDIuOTc2MjcsMGwyLjE1NzgsMi4xNTc4YzAuODIxODcsMC44MjE4OCAwLjgyMTg3LDIuMTU0MzkgMCwyLjk3NjI3bC0yLjE1NzgsMi4xNTc4Yy0wLjgyMTg3LDAuODIxODggLTIuMTU0MzksMC44MjE4OCAtMi45NzYyNywweiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yMTMuNTM1OTcsMjA0LjMwNjIzYy0wLjgyMTg4LC0wLjgyMTg4IC0wLjgyMTg4LC0yLjE1NDM5IDAsLTIuOTc2MjdsMi4xNTc4LC0yLjE1NzhjMC44MjE4OCwtMC44MjE4OCAyLjE1NDM5LC0wLjgyMTg4IDIuOTc2MjcsMGwyLjE1NzgsMi4xNTc4YzAuODIxODgsMC44MjE4OCAwLjgyMTg4LDIuMTU0MzkgMCwyLjk3NjI3bC0yLjE1NzgsMi4xNTc4Yy0wLjgyMTg4LDAuODIxODggLTIuMTU0MzksMC44MjE4OCAtMi45NzYyNywweiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yNjguNjM5MzMsMTc4LjQ3NDJjMCwtMS4xNjIzMSAwLjk0MjI0LC0yLjEwNDUzIDIuMTA0NTQsLTIuMTA0NTNoMy4wNTE1OWMxLjE2MjMxLDAgMi4xMDQ1NCwwLjk0MjIzIDIuMTA0NTQsMi4xMDQ1M3YzLjA1MTZjMCwxLjE2MjMxIC0wLjk0MjIzLDIuMTA0NTMgLTIuMTA0NTQsMi4xMDQ1M2gtMy4wNTE1OWMtMS4xNjIzMSwwIC0yLjEwNDU0LC0wLjk0MjIzIC0yLjEwNDU0LC0yLjEwNDUzeiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yMDQuMSwxNzguNDc0MmMwLC0xLjE2MjMxIDAuOTQyMjQsLTIuMTA0NTMgMi4xMDQ1NCwtMi4xMDQ1M2gzLjA1MTU5YzEuMTYyMzEsMCAyLjEwNDU0LDAuOTQyMjMgMi4xMDQ1NCwyLjEwNDUzdjMuMDUxNmMwLDEuMTYyMzEgLTAuOTQyMjMsMi4xMDQ1MyAtMi4xMDQ1NCwyLjEwNDUzaC0zLjA1MTU5Yy0xLjE2MjMxLDAgLTIuMTA0NTQsLTAuOTQyMjMgLTIuMTA0NTQsLTIuMTA0NTN6IiBmaWxsPSIjZmZmZmZmIi8+PHBhdGggZD0iTTI2MS4zMjk5NywxOTkuMTcyMTZjMC44MjE4NywtMC44MjE4OCAyLjE1NDM5LC0wLjgyMTg4IDIuOTc2MjcsMGwyLjE1NzgsMi4xNTc4YzAuODIxODcsMC44MjE4OCAwLjgyMTg3LDIuMTU0MzkgMCwyLjk3NjI3bC0yLjE1NzgsMi4xNTc4Yy0wLjgyMTg3LDAuODIxODggLTIuMTU0MzksMC44MjE4OCAtMi45NzYyNywwbC0yLjE1NzgsLTIuMTU3OGMtMC44MjE4NywtMC44MjE4OCAtMC44MjE4NywtMi4xNTQzOSAwLC0yLjk3NjI3eiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yMTUuNjkzNzcsMTUzLjUzNTk3YzAuODIxODgsLTAuODIxODggMi4xNTQzOSwtMC44MjE4OCAyLjk3NjI3LDBsMi4xNTc4LDIuMTU3OGMwLjgyMTg4LDAuODIxODggMC44MjE4OCwyLjE1NDM5IDAsMi45NzYyN2wtMi4xNTc4LDIuMTU3OGMtMC44MjE4OCwwLjgyMTg4IC0yLjE1NDM5LDAuODIxODggLTIuOTc2MjcsMGwtMi4xNTc4LC0yLjE1NzhjLTAuODIxODgsLTAuODIxODggLTAuODIxODgsLTIuMTU0MzkgMCwtMi45NzYyN3oiIGZpbGw9IiNmZmZmZmYiLz48cGF0aCBkPSJNMjI0LjEwOTEyLDE2My45NjYyMmM3LjU0NzAyLC03LjU0NzAyIDE5LjA2NTQyLC04LjcwMjI0IDI3LjgyODIzLC0zLjQ2NTYzYy0wLjEyNTAxLDAuMDkwMSAtMTMuMjY0MjYsMTcuODcyNDcgLTEyLjEzMzc5LDE5LjAwMjkzbDEuMDM3OTIsMS4wMzc5MmMxLjEzMDQ3LDEuMTMwNDcgMTguOTEyODMsLTEyLjAwODc4IDE5LjAwMjkzLC0xMi4xMzM3OWM1LjIzNjYxLDguNzYyODEgNC4wODE0LDIwLjI4MTIgLTMuNDY1NjMsMjcuODI4MjNjLTguOTExMDIsOC45MTEwMiAtMjMuMzU4NjUsOC45MTEwMiAtMzIuMjY5NjcsMGMtOC45MTEwMiwtOC45MTEwMiAtOC45MTEwMiwtMjMuMzU4NjQgMCwtMzIuMjY5NjZ6IiBmaWxsPSIjZmZmZmZmIi8+PC9nPjwvZz48L3N2Zz4="; +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0wIDc4Ljk3NFYwaDc4Ljk3NHY3OC45NzR6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTM3Ljk2MSAxMC44NDdhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDRWNS42OTJjMC0xLjE2My45NDItMi4xMDUgMi4xMDQtMi4xMDVoMy4wNTJjMS4xNjIgMCAyLjEwNC45NDIgMi4xMDQgMi4xMDV2My4wNTFhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQgMi4xMDV6bTAgNjQuNTRhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDV2LTMuMDUxYzAtMS4xNjIuOTQyLTIuMTA1IDIuMTA0LTIuMTA1aDMuMDUyYzEuMTYyIDAgMi4xMDQuOTQzIDIuMTA0IDIuMTA1djMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA0IDIuMTA1em0yMC42OTgtNTcuMjNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6TTEzLjAyMyA2My43OTNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6bTU1LjEwNC0yNS44MzJjMC0xLjE2Mi45NDItMi4xMDQgMi4xMDQtMi4xMDRoMy4wNTFjMS4xNjMgMCAyLjEwNS45NDIgMi4xMDUgMi4xMDR2My4wNTJhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDUgMi4xMDRoLTMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA1LTIuMTA0em0tNjQuNTQgMGMwLTEuMTYyLjk0Mi0yLjEwNCAyLjEwNS0yLjEwNGgzLjA1MWMxLjE2MiAwIDIuMTA1Ljk0MiAyLjEwNSAyLjEwNHYzLjA1MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNSAyLjEwNEg1LjY5MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNS0yLjEwNHptNTcuMjMgMjAuNjk4YTIuMTA1IDIuMTA1IDAgMCAxIDIuOTc2IDBsMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMCAyLjk3NmwtMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEtMi45NzYgMGwtMi4xNTgtMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NnpNMTUuMTgxIDEzLjAyM2EyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDBsLTIuMTU4LTIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAtMi45NzZ6bTguNDE2IDEwLjQzYzcuNTQ2LTcuNTQ3IDE5LjA2NS04LjcwMiAyNy44MjctMy40NjUtLjEyNS4wOS0xMy4yNjQgMTcuODcyLTEyLjEzMyAxOS4wMDNsMS4wMzcgMS4wMzdjMS4xMyAxLjEzIDE4LjkxMy0xMi4wMDggMTkuMDAzLTEyLjEzMyA1LjIzNyA4Ljc2MiA0LjA4MiAyMC4yOC0zLjQ2NSAyNy44MjgtOC45MTEgOC45MS0yMy4zNTkgOC45MS0zMi4yNyAwLTguOTEtOC45MTEtOC45MS0yMy4zNTkgMC0zMi4yNyIgZmlsbD0iI2ZmZiIvPjwvZz48L3N2Zz4="; const stopSign = -"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjEuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNCAxNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTQgMTQ7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRUM1OTU5O3N0cm9rZTojQjg0ODQ4O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9Cjwvc3R5bGU+Cjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNC4zLDAuNSA5LjcsMC41IDEzLjUsNC4zIDEzLjUsOS43IDkuNywxMy41IDQuMywxMy41IDAuNSw5LjcgMC41LDQuMyAiLz4KPC9zdmc+Cg=="; +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNCAxNCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6I2VjNTk1OTtzdHJva2U6I2I4NDg0ODtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIGQ9Ik00LjMuNWg1LjRsMy44IDMuOHY1LjRsLTMuOCAzLjhINC4zTC41IDkuN1Y0LjN6Ii8+PC9zdmc+"; const startFlag = -"data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48ZGVmcz48c3R5bGU+LmNscy0xLC5jbHMtMntmaWxsOiM0Y2JmNTY7c3Ryb2tlOiM0NTk5M2Q7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO30uY2xzLTJ7c3Ryb2tlLXdpZHRoOjEuNXB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tZ3JlZW4tZmxhZzwvdGl0bGU+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNLjc1LDJBNi40NCw2LjQ0LDAsMCwxLDguNDQsMmgwYTYuNDQsNi40NCwwLDAsMCw3LjY5LDBWMTIuNGE2LjQ0LDYuNDQsMCwwLDEtNy42OSwwaDBhNi40NCw2LjQ0LDAsMCwwLTcuNjksMCIvPjxsaW5lIGNsYXNzPSJjbHMtMiIgeDE9IjAuNzUiIHkxPSIxNi43NSIgeDI9IjAuNzUiIHkyPSIwLjc1Ii8+PC9zdmc+"; +"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48cGF0aCBkPSJNLjc1IDJhNi40NCA2LjQ0IDAgMCAxIDcuNjkgMGgwYTYuNDQgNi40NCAwIDAgMCA3LjY5IDB2MTAuNGE2LjQ0IDYuNDQgMCAwIDEtNy42OSAwaDBhNi40NCA2LjQ0IDAgMCAwLTcuNjkgMCIgc3R5bGU9ImZpbGw6IzRjYmY1NjtzdHJva2U6IzQ1OTkzZDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQiLz48cGF0aCBzdHlsZT0iZmlsbDojNGNiZjU2O3N0cm9rZTojNDU5OTNkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MS41cHgiIGQ9Ik0uNzUgMTYuNzV2LTE2Ii8+PC9zdmc+"; // Modified Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) // uses MIT License @@ -64,7 +65,7 @@ }); } }; - load(runtime.extensionStorage["SPtuneShark3"]); + if (!Scratch.extensions.isPenguinMod) load(runtime.extensionStorage["SPtuneShark3"]); class SPtuneShark3 { constructor() { @@ -83,7 +84,6 @@ // Clamp Volume to Project Volume const curVol = Math.min(100, Math.max(0, bank.vol)) / 100; sound.volume = curVol * projectVal; - // Apply Speed Changes if (bank.speed !== 1 && sound.playing) { const lastplay = sound.lastTimePlayed; @@ -123,7 +123,7 @@ text: "import sound from URL [URL] named [NAME]", blockIconURI: settingsIconURI, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, @@ -490,15 +490,18 @@ playMenu: ["start", "stop", "pause", "unpause"], toggleMenu: ["on", "off"], bindMenu: ["bind", "unBind"], - loudProps: ["loudness", "raw noise"], + loudProps: ["loudness", "raw noise", "tone"], typePass: ["highpass", "lowpass"], - singleEffects: ["pitch", "detune", "speed", "pan", "gain", "distortion"], + singleEffects: [ + "pitch", "detune", "speed", "pan", + "gain", "distortion", "attack", "release" + ], soundProps: { acceptReporters: true, items: [ - "length", "current time", "source", "binds", "volume", "pitch", "detune", - "speed", "pan", "gain", "distortion", "reverb", "delay", "tremolo", "fuzz", - "bitcrush", "highpass", "lowpass", "flanger", "compressor", "equalizer" + "length", "current time", "estimated bpm", "source", "binds", "volume", "pitch", "detune", + "speed", "pan", "gain", "distortion", "attack", "release", "reverb", "delay", "tremolo", + "fuzz", "bitcrush", "highpass", "lowpass", "flanger", "compressor", "equalizer" ] }, soundBools: { @@ -509,8 +512,8 @@ acceptReporters: true, items: [ "all effects", "pitch", "detune", "speed", "pan", "gain", "distortion", - "reverb", "delay", "tremolo", "fuzz", "bitcrush", "highpass", "lowpass", - "flanger", "compressor", "equalizer" + "attack", "release", "reverb", "delay", "tremolo", "fuzz", "bitcrush", + "highpass", "lowpass", "flanger", "compressor", "equalizer" ] } }, @@ -531,9 +534,7 @@ return Math.min(leng, Math.max(0, time)); } - modTime(number, opts) { - return number / (opts.pitch * opts.speed * ((opts.detune / 1000) + 1)); - } + modTime(number, opts) { return number / (opts.pitch * opts.speed * ((opts.detune / 1000) + 1)) } updateEffect(effect, sound, name, args) { delete args.NAME; @@ -589,6 +590,24 @@ } } + getBPM(data, sampleRate) { + const peaks = []; + let lastPeakIndex = 0; + let max = 0.1; + for (let i = 0; i < data.length; i++) { if (data[i] > max) max = data[i] } + for (let i = 0; i < data.length; i++) { + if (data[i] > max - 0.1 && i - lastPeakIndex > sampleRate / 4) { + peaks.push(i); + lastPeakIndex = i; + } + } + const intervals = []; + for (let i = 1; i < peaks.length; i++) { intervals.push(peaks[i] - peaks[i - 1]) } + const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length; + const value = Math.round((sampleRate / avgInterval) * 60); + return isNaN(value) ? 0 : value; + } + play(sound, atTime, con) { try { if (sound.playing && con.overlap) { @@ -734,7 +753,7 @@ const time = Scratch.Cast.toNumber(args.TIME); const max = Scratch.Cast.toNumber(args.MAX); this.play(sound.context, time, sound); - await new Promise((resolve, reject) => { + await new Promise((resolve) => { setTimeout(() => { this.typeOverlay(sound, "stop"); resolve(); @@ -789,9 +808,7 @@ this.typeOverlay(sound, "stop"); const node = sound.context.sourceNode; const reverseBuffer = (audioBuffer) => { - for (let i = 0; i < audioBuffer.numberOfChannels; i++) { - audioBuffer.getChannelData(i).reverse(); - } + for (let i = 0; i < audioBuffer.numberOfChannels; i++) { audioBuffer.getChannelData(i).reverse() } return audioBuffer; } const bufferSource = node.context.createBufferSource(); @@ -802,7 +819,7 @@ loopParams(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - sound.context.loop = true; // Auto-turn it on + sound.context.loop = true; const srcNode = sound.context.sourceNode; srcNode.loopStart = Scratch.Cast.toNumber(args.START); srcNode.loopEnd = Scratch.Cast.toNumber(args.END); @@ -855,6 +872,7 @@ sound.context.loop && sound.loopParm[1] ? sound.loopParm[1] : src.buffer.duration, sound.context.lastTimePlayed, src.context.currentTime, sound ); + case "estimated bpm": return this.getBPM(src.buffer.getChannelData(0), src.buffer.sampleRate); case "source": return sound.src; case "binds": return JSON.stringify(Object.keys(sound.binds)); case "volume": return sound.vol; @@ -864,6 +882,8 @@ case "gain": return sound.gain * 100; case "pan": return sound.effects[args.PROP.toUpperCase()]?.options.pan * 100 || 0; case "distortion": return sound.effects[args.PROP.toUpperCase()]?.options.gain * 100 || 0; + case "attack": return sound.context.attack * 100; + case "release": return sound.context.release * 100; default: { const effect = sound.effects[args.PROP.toUpperCase()]; if (effect === undefined) return ""; @@ -882,17 +902,38 @@ const sampleRate = audioBuffer.sampleRate; const channelData = audioBuffer.getChannelData(0); const sampleIndex = Math.floor(sampleRate * time); - const windowSize = sampleRate * 0.1; const startSample = Math.max(0, sampleIndex - windowSize / 2); const endSample = Math.min(channelData.length, sampleIndex + windowSize / 2); - let sample = 0; if (args.TYPE === "raw noise") sample = channelData[endSample]; - else { - for (let i = startSample; i < endSample; i++) { - sample += channelData[i] * channelData[i]; + else if (args.TYPE === "tone") { + const data = channelData.slice(startSample, endSample); + const size = data.length; + const tauArray = new Array(size).fill(0); + for (let tau = 1; tau < size; tau++) { + let sum = 0; + for (let i = 0; i < size - tau; i++) { + const diff = data[i] - data[i + tau]; + sum += diff * diff; + } + tauArray[tau] = sum; + } + for (let tau = 1; tau < size; tau++) { + sample += tauArray[tau]; + tauArray[tau] *= tau / sample; } + let bestTau = -1; + for (let tau = 1; tau < size; tau++) { + if (tauArray[tau] < 0.1) { + bestTau = tau; + break; + } + } + if (bestTau > 0) return sampleRate / bestTau; + return 0; + } else { + for (let i = startSample; i < endSample; i++) { sample += channelData[i] * channelData[i] } const rms = Math.sqrt(sample / (endSample - startSample)); const dB = 20 * Math.log10(rms); sample = Math.min(Math.max((dB + 50) / 50, 0), 1) * 100; @@ -918,6 +959,8 @@ if (args.EFFECT === "all effects" || args.EFFECT === "detune") sound.detune = 0; if (args.EFFECT === "all effects" || args.EFFECT === "speed") sound.speed = 1; if (args.EFFECT === "all effects" || args.EFFECT === "gain") sound.gain = 1; + if (args.EFFECT === "all effects" || args.EFFECT === "attack") sound.context.attack = 0; + if (args.EFFECT === "all effects" || args.EFFECT === "release") sound.context.release = 0; const name = args.EFFECT.toUpperCase(); if (sound.effects[name] !== undefined) { sound.context.removeEffect(sound.effects[name]); @@ -934,6 +977,8 @@ else if (args.TYPE === "detune") sound.detune = value * 1000; else if (args.TYPE === "speed") sound.speed = Math.max(0, value); else if (args.TYPE === "gain") sound.gain = value; + else if (args.TYPE === "attack") sound.context.attack = value; + else if (args.TYPE === "release") sound.context.release = value; else if (args.TYPE === "pan") { const pan = new Pizzicato.Effects.StereoPanner({ pan: Math.max(-1, Math.min(1, value)) }); return this.updateEffect(pan, sound, "PAN", args); @@ -1006,15 +1051,12 @@ setPass(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; + const json = { frequency: Scratch.Cast.toNumber(args.FREQ), peak:Scratch.Cast.toNumber(args.PEAK) / 5 }; if (args.TYPE === "highpass") { - const highpass = new Pizzicato.Effects.LowPassFilter({ - frequency: Scratch.Cast.toNumber(args.FREQ), peak:Scratch.Cast.toNumber(args.PEAK) / 5 - }); + const highpass = new Pizzicato.Effects.HighPassFilter(json); this.updateEffect(highpass, sound, "HIGHPASS", args); } else { - const lowpass = new Pizzicato.Effects.LowPassFilter({ - frequency: Scratch.Cast.toNumber(args.FREQ), peak: Scratch.Cast.toNumber(args.PEAK) / 5 - }); + const lowpass = new Pizzicato.Effects.LowPassFilter(json); this.updateEffect(lowpass, sound, "LOWPASS", args); } } @@ -1064,6 +1106,16 @@ } else { runtime.extensionStorage["SPtuneShark3"] = undefined } } } + + // PenguinMod Storage + serialize() { + if (settings.canSave) { + const convertBank = JSON.parse(JSON.stringify(soundBank)); + Object.values(convertBank).forEach(item => delete item.context); + return { SPtuneShark3 : { bank : convertBank, settings } } + } + } + deserialize(data) { load(data.SPtuneShark3) } } Scratch.extensions.register(new SPtuneShark3()); From 6ca5f425f9df9379f9db75c6951e490e16f30c5a Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:00:46 -0700 Subject: [PATCH 17/40] Update Tune-Shark-V3 -- Forgot to Replace Sound URL --- extensions/SharkPool/Tune-Shark-V3.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 43bc0964fb..bceae00b33 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -5,7 +5,6 @@ // License: MIT AND LGPL-3.0 // Version V.3.3.0 -// Thanks to HOME for the song "Resonance" being used as the default audio link (function (Scratch) { "use strict"; @@ -123,7 +122,7 @@ text: "import sound from URL [URL] named [NAME]", blockIconURI: settingsIconURI, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, From 0ad51c10297e32b67217727e00a02ea33fb78a06 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:08:42 -0700 Subject: [PATCH 18/40] Tune-Shark-V3.3 -- Quick Patch --- extensions/SharkPool/Tune-Shark-V3.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index bceae00b33..8e294547fe 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -5,6 +5,7 @@ // License: MIT AND LGPL-3.0 // Version V.3.3.0 +// Thanks to HOME for the song "Resonance" being used as the default audio link (function (Scratch) { "use strict"; @@ -122,7 +123,7 @@ text: "import sound from URL [URL] named [NAME]", blockIconURI: settingsIconURI, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, @@ -976,8 +977,8 @@ else if (args.TYPE === "detune") sound.detune = value * 1000; else if (args.TYPE === "speed") sound.speed = Math.max(0, value); else if (args.TYPE === "gain") sound.gain = value; - else if (args.TYPE === "attack") sound.context.attack = value; - else if (args.TYPE === "release") sound.context.release = value; + else if (args.TYPE === "attack") sound.context.attack = Math.max(0, value); + else if (args.TYPE === "release") sound.context.release = Math.max(0, value); else if (args.TYPE === "pan") { const pan = new Pizzicato.Effects.StereoPanner({ pan: Math.max(-1, Math.min(1, value)) }); return this.updateEffect(pan, sound, "PAN", args); From 278e14c1c191830e26620ab0af903d557949fd65 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:11:42 -0700 Subject: [PATCH 19/40] bleh replace link again --- extensions/SharkPool/Tune-Shark-V3.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 8e294547fe..43c5965cb2 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -5,7 +5,6 @@ // License: MIT AND LGPL-3.0 // Version V.3.3.0 -// Thanks to HOME for the song "Resonance" being used as the default audio link (function (Scratch) { "use strict"; @@ -123,7 +122,7 @@ text: "import sound from URL [URL] named [NAME]", blockIconURI: settingsIconURI, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://tinyurl.com/Resonance-Home" }, + URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, From 270ca3619ff96e11256371dc49a8a1b31eb81703 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Wed, 31 Jul 2024 21:26:18 -0700 Subject: [PATCH 20/40] Update Tune-Shark-V3.js --- extensions/SharkPool/Tune-Shark-V3.js | 49 ++++++++++++++------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 43c5965cb2..eca8760d6e 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,16 +4,12 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.3.0 +// Version V.3.3.01 (function (Scratch) { "use strict"; if (!Scratch.extensions.unsandboxed) throw new Error("Tune Shark V3 must be run unsandboxed"); - const vm = Scratch.vm; - const runtime = vm.runtime; - const scratchAudio = runtime.audioEngine; - const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDIuMTg1IiBoZWlnaHQ9IjEwMi4xODUiIHZpZXdCb3g9IjAgMCAxMDIuMTg1IDEwMi4xODUiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgNTEuMDkzQzAgMjIuODc1IDIyLjg3NSAwIDUxLjA5MyAwczUxLjA5MyAyMi44NzUgNTEuMDkzIDUxLjA5My0yMi44NzUgNTEuMDkzLTUxLjA5MyA1MS4wOTNTMCA3OS4zMTEgMCA1MS4wOTMiIGZpbGw9IiM0MDQwNDAiLz48cGF0aCBkPSJNNC44NiA1MS4wOTNjMC0yNS41MzQgMjAuNy00Ni4yMzMgNDYuMjMzLTQ2LjIzMyAyNS41MzQgMCA0Ni4yMzMgMjAuNyA0Ni4yMzMgNDYuMjMzIDAgMjUuNTM0LTIwLjcgNDYuMjMzLTQ2LjIzMyA0Ni4yMzMtMjUuNTM0IDAtNDYuMjMzLTIwLjctNDYuMjMzLTQ2LjIzMyIgZmlsbD0iIzY2NiIvPjxwYXRoIGQ9Ik03Mi44MzcgODYuNjQzdi0uMDAzYy0xLjI1NCAyLjUzNi00LjY2OCAzLjkzNS04LjI2NCAzLjE5Ny00LjExOC0uODQ0LTYuOTE1LTQuMTctNi4yNDYtNy40MjguNjY4LTMuMjYgNC41NDgtNS4yMTYgOC42NjYtNC4zNzEgMS44NzUuMzg0IDMuNDc0IDEuMjg0IDQuNiAyLjQ1N2w2LjY4My0xNC4xNzhjLTEwLjU2Ni00LjEzNS0xOS43Ni01LjA5Ni0xOS43Ni01LjA5NmwtOC45ODcgMTkuMDYxYy0uOTY2IDIuOTI3LTQuNjM2IDQuNjIyLTguNTIgMy44MjYtNC4xMTctLjg0NC02LjkxNC00LjE3LTYuMjQ2LTcuNDMuNjY5LTMuMjU4IDQuNTQ4LTUuMjE0IDguNjY3LTQuMzcgMS45MS4zOTEgMy41MzYgMS4zMTcgNC42NjQgMi41MjJsMTIuMDM1LTI1LjUwN3MxMy41MzIuMjM2IDI2Ljk0NyA3LjExNHoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtMjguMzA5IDMwLjgzMSA0LjA0MyAyMy42ODQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIi8+PHBhdGggZD0iTTM0LjAzMyA1NS4yMTljMS4zOCAyLjYwNi0uNzcyIDYuMDQtNC44MDggNy42Ny00LjAzNyAxLjYzLTguNDI5LjgzNy05LjgxLTEuNzctMS4zOC0yLjYwNi43NzItNi4wNCA0LjgwOS03LjY3IDQuMDM2LTEuNjI5IDguNDI4LS44MzcgOS44MDkgMS43N20tNy45NS0yNy4wNjhzOS43MDUtMS43MDQgMTIuMzYzIDIuNzdjMi4zNzUgNCAuMDcxIDguNjk1LjIxMiAxMC4xMjguMTQgMS40MzMgMi4xNzUgMS4xMDkgMi4xNzUgMS4xMDlsLS4wMTQgMS42NzRzLTIuODY0LjY2OS0zLjQxMi0xLjMyYy0uNTQ3LTEuOTg4LS41Ni01Ljk3OC0yLjgyMy04LjIyNy0yLjI2NS0yLjI1LTcuNTM3LS43NTktNy41MzctLjc1OSIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik02MC40MzYgMzUuMzcxYy0uMzMzIDIuMTE4LTIuMzUzIDMuMzA5LTQuMTg5IDQuMDAyLTEuNjUuNzI2LTMuNTUgMS4wMDgtNS4yNzYuMzctMS42MzEtLjM4MS0zLjE4OC0xLjgwNy0yLjk5NC0zLjU5OC4xNTQtMi4wODUgMS44OTYtMy44MjIgMy44NDItNC40MDUgMS45NC0uNzk5IDQuMjUzLS43MyA2LjEwNS4yNi45MTMuNDIxIDIuMTg5LTE0LjE0MiAzLjAzNS0yMC41ODMuMS0uNjQyIDIuNTg0LS40NyAyLjUxMy4xNTEgMCAwLTEuODU0IDE1LjUyMi0zLjAzNiAyMy44MDMiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4="; const blockIconURI = @@ -37,17 +33,13 @@ document.body.appendChild(scriptElement); /* global Pizzicato */ - // Create an Event for when Pause Project is Activated - // Save original function if it exists - let ogPauseFunc = Object.getOwnPropertyDescriptor(runtime.ioDevices.clock, "_paused")?.set; - Object.defineProperty(runtime.ioDevices.clock, "_paused", { - set: function(value) { - this._pausedValue = value; - runtime.emit("SP_TUNE3_PROJECT_PAUSE", value); - if (ogPauseFunc) ogPauseFunc.call(this, value); - }, - get: function() { return this._pausedValue } - }); + const vm = Scratch.vm; + const runtime = vm.runtime; + const scratchAudio = runtime.audioEngine; + const allSingleEffects = [ + "pitch", "detune", "speed", "pan", + "gain", "distortion", "attack", "release" + ]; let soundBank = {}; let settings = { flagCtrl : false, canSave : false }; @@ -66,6 +58,18 @@ }; if (!Scratch.extensions.isPenguinMod) load(runtime.extensionStorage["SPtuneShark3"]); + // Create an Event for when Pause Project is Activated + // Save original function if it exists + let ogPauseFunc = Object.getOwnPropertyDescriptor(runtime.ioDevices.clock, "_paused")?.set; + Object.defineProperty(runtime.ioDevices.clock, "_paused", { + set: function(value) { + this._pausedValue = value; + runtime.emit("SP_TUNE3_PROJECT_PAUSE", value); + if (ogPauseFunc) ogPauseFunc.call(this, value); + }, + get: function() { return this._pausedValue } + }); + class SPtuneShark3 { constructor() { runtime.on("PROJECT_START", () => { @@ -356,13 +360,13 @@ }, "---", { - opcode: "setThing", + opcode: "setThingNew", blockType: Scratch.BlockType.COMMAND, text: "set [TYPE] of sound [NAME] to [VALUE]", blockIconURI: nobIconURI, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - TYPE: { type: Scratch.ArgumentType.STRING, menu: "singleEffects" }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "singleEffectNew" }, VALUE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } }, }, @@ -491,10 +495,7 @@ bindMenu: ["bind", "unBind"], loudProps: ["loudness", "raw noise", "tone"], typePass: ["highpass", "lowpass"], - singleEffects: [ - "pitch", "detune", "speed", "pan", - "gain", "distortion", "attack", "release" - ], + singleEffectNew: { acceptReporters: true, items: allSingleEffects }, soundProps: { acceptReporters: true, items: [ @@ -968,7 +969,7 @@ this.patchLinks(sound.context.sourceNode, sound); } - setThing(args) { + setThingNew(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; const value = Scratch.Cast.toNumber(args.VALUE) / 100; @@ -981,7 +982,7 @@ else if (args.TYPE === "pan") { const pan = new Pizzicato.Effects.StereoPanner({ pan: Math.max(-1, Math.min(1, value)) }); return this.updateEffect(pan, sound, "PAN", args); - } else { + } else if (args.TYPE === "distortion") { const distort = new Pizzicato.Effects.Distortion({ gain: value }); return this.updateEffect(distort, sound, "DISTORTION", args); } From 2bfc3f195ff58f28a5d403543191ba43460273e6 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Fri, 23 Aug 2024 21:45:55 -0700 Subject: [PATCH 21/40] Tune-Shark-V3 -- Pause & Current Time Improvements --- extensions/SharkPool/Tune-Shark-V3.js | 99 +++++++++++++-------------- 1 file changed, 49 insertions(+), 50 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index eca8760d6e..bd8fc15652 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.3.01 +// Version V.3.3.02 (function (Scratch) { "use strict"; @@ -100,16 +100,8 @@ }); }); runtime.on("SP_TUNE3_PROJECT_PAUSE", () => { - if (runtime.ioDevices.clock._paused) Object.keys(soundBank).forEach(key => { soundBank[key].context.pause() }); - else { - Object.keys(soundBank).forEach(key => { - const thisSound = soundBank[key].context; - if (thisSound.paused) { - thisSound.play(); - this.patchLinks(thisSound.sourceNode, soundBank[key]); - } - }); - } + if (runtime.ioDevices.clock._paused) this.ctrlSounds({ CONTROL: "pause" }); + else this.ctrlSounds({ CONTROL: "unpause" }); }); } getInfo() { @@ -359,6 +351,11 @@ }, }, "---", + { + opcode: "setThing", blockType: Scratch.BlockType.COMMAND, hideFromPalette: true, // deprecated + text: "set [TYPE] of sound [NAME] to [VALUE]", blockIconURI: nobIconURI, + arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "singleEffects" }, VALUE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } }, + }, { opcode: "setThingNew", blockType: Scratch.BlockType.COMMAND, @@ -488,6 +485,7 @@ } ], menus: { + singleEffects: allSingleEffects, // deprecated saveMenu: ["save", "dont save"], un_pauseMenu: ["pause", "unpause"], playMenu: ["start", "stop", "pause", "unpause"], @@ -523,22 +521,24 @@ // Helper Funcs getSoundIndex(name, target) { const sounds = target.sounds; - return sounds.indexOf(sounds.filter((sound) => { return sound.name === name })[0]); + return sounds.indexOf(sounds.filter((i) => { return i.name === name })[0]); } - calcTime(leng, start, currentT, sound) { + currentTime(sound, ctx, src) { + if (!ctx.playing && !ctx.paused) return 0; + let leng = ctx.loop && sound.loopParm[1] ? sound.loopParm[1] : src.buffer.duration; leng = this.modTime(leng, sound); const loopStart = sound.loopParm[0]; - let time = Math.abs(start - currentT); - if (sound.context.loop) return (Math.max(0, time % (leng - loopStart)) + loopStart); + const now = ctx.paused ? sound.pauseTime : src.context.currentTime; + let time = Math.abs(ctx.lastTimePlayed - now); + if (ctx.loop) return (Math.max(0, time % (leng - loopStart)) + loopStart); return Math.min(leng, Math.max(0, time)); } modTime(number, opts) { return number / (opts.pitch * opts.speed * ((opts.detune / 1000) + 1)) } updateEffect(effect, sound, name, args) { - delete args.NAME; - delete args.TYPE; + delete args.NAME; delete args.TYPE; effect.arguments = args; // Match Original Values, not Converted if (sound.effects[name] === undefined) { effect.id = name; @@ -615,14 +615,14 @@ const newName = `${con.name}_COPY_${Math.random()}`; soundBank[newName] = { ...sound, - context: clone, name: newName, loopParm: [0, 0], overlap: false, - overlays: [], isBind: false, binds: {} + context: clone, name: newName, loopParm: [0, 0], pauseTime: 0, + overlap: false, overlays: [], isBind: false, binds: {} }; clone.play(); clone.sourceNode.playbackRate.value = con.pitch; clone.sourceNode.gainSuccessor.gain.value = con.gain; con.overlays.push(clone); - clone.on("end", function() { delete soundBank[newName] }); + clone.on("end", () => { delete soundBank[newName] }); } else { sound.play(0, sound.loop ? con.loopParm[0] : atTime); const srcNode = sound.sourceNode; @@ -644,15 +644,25 @@ } typeOverlay(sound, type) { + const ctx = sound.context; + const src = ctx.sourceNode; if (type === "stop") { - sound.context.stop(); + ctx.stop(); for (let i = 0; i < sound.overlays.length; i++) { sound.overlays[i].stop() } } else if (type === "pause") { - sound.context.pause(); + sound.pauseTime = src.context.currentTime; + ctx.pause(); for (let i = 0; i < sound.overlays.length; i++) { sound.overlays[i].pause() } - } else if (type === "play") { - sound.context.play(); - this.patchLinks(sound.context.sourceNode, sound); + } else { + if (type === "play") ctx.play(); + else { + if (!ctx.paused) return; + const lastTime = this.currentTime(sound, ctx, src); + ctx.stop(); + ctx.play(0, lastTime); + return this.patchLinks(ctx.sourceNode, sound); + } + this.patchLinks(src, sound); for (let i = 0; i < sound.overlays.length; i++) { sound.overlays[i].play(); this.patchLinks(sound.overlays[i].sourceNode, sound); @@ -677,7 +687,7 @@ engine.sourceNode = engine.getSourceNode(); engine.attack = 0; soundBank[args.NAME] = { - context: engine, name: args.NAME, src: args.URL, effects: {}, + context: engine, name: args.NAME, src: args.URL, effects: {}, pauseTime: 0, loaded: true, reversed: false, vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, loopParm: [0, 0], overlap: false, overlays: [], isBind: false, binds: {} }; @@ -715,7 +725,7 @@ reader.readAsDataURL(audioBlob); }); await this.importURL({ NAME: args.NAME2, URL: audioDataURL }, util); - } catch (error) { console.error(error.message) } + } catch (e) { console.error(e) } } bindSound(args) { @@ -723,16 +733,13 @@ const sound2 = soundBank[args.NAME2]; if (sound1 === undefined || sound2 === undefined) return; const shouldBind = args.TYPE === "bind"; - sound1.isBind = shouldBind; - sound2.isBind = shouldBind; + sound1.isBind = shouldBind; sound2.isBind = shouldBind; if (shouldBind) { if (sound1.binds[sound2.name]) this.typeOverlay(sound1.binds[sound2.name], "stop"); if (sound2.binds[sound1.name]) this.typeOverlay(sound2.binds[sound1.name], "stop"); - sound1.binds[sound2.name] = sound2; - sound2.binds[sound1.name] = sound1; + sound1.binds[sound2.name] = sound2; sound2.binds[sound1.name] = sound1; } else { - delete sound1.binds[sound2.name]; - delete sound2.binds[sound1.name]; + delete sound1.binds[sound2.name]; delete sound2.binds[sound1.name]; } } @@ -770,19 +777,14 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; if (args.UN_PAUSE === "pause") this.typeOverlay(sound, "pause"); - else this.typeOverlay(sound, "play"); + else this.typeOverlay(sound, "unpause"); } ctrlSounds(args) { if (args.CONTROL === "start") Object.keys(soundBank).forEach(key => { this.play(soundBank[key].context, 0, soundBank[key]) }); else if (args.CONTROL === "stop") Object.keys(soundBank).forEach(key => { soundBank[key].context.stop() }); - else if (args.CONTROL === "pause") Object.keys(soundBank).forEach(key => { soundBank[key].context.pause() }); - else { - Object.keys(soundBank).forEach(key => { - soundBank[key].context.play(); - this.patchLinks(soundBank[key].context.sourceNode, soundBank[key]); - }); - } + else if (args.CONTROL === "pause") Object.keys(soundBank).forEach(key => { this.typeOverlay(soundBank[key], "pause") }); + else Object.keys(soundBank).forEach(key => { this.typeOverlay(soundBank[key], "unpause") }); } enableControl(args) { settings.flagCtrl = args.ON_OFF === "on" } @@ -807,9 +809,9 @@ sound.reversed = args.TYPE === "on"; this.typeOverlay(sound, "stop"); const node = sound.context.sourceNode; - const reverseBuffer = (audioBuffer) => { - for (let i = 0; i < audioBuffer.numberOfChannels; i++) { audioBuffer.getChannelData(i).reverse() } - return audioBuffer; + const reverseBuffer = (buffer) => { + for (let i = 0; i < buffer.numberOfChannels; i++) { buffer.getChannelData(i).reverse() } + return buffer; } const bufferSource = node.context.createBufferSource(); bufferSource.buffer = reverseBuffer(node.buffer); @@ -832,7 +834,7 @@ } deleteAllSounds() { - this.ctrlSounds({ CONTROL: "stop" }); + this.ctrlSounds({ CONTROL: "stop" }); soundBank = {}; } @@ -867,11 +869,7 @@ const src = sound.context.sourceNode; switch (args.PROP) { case "length": return this.modTime(src.buffer.duration, sound); - case "current time": return !sound.context.playing ? 0 : - this.calcTime( - sound.context.loop && sound.loopParm[1] ? sound.loopParm[1] : src.buffer.duration, - sound.context.lastTimePlayed, src.context.currentTime, sound - ); + case "current time": return this.currentTime(sound, sound.context, src); case "estimated bpm": return this.getBPM(src.buffer.getChannelData(0), src.buffer.sampleRate); case "source": return sound.src; case "binds": return JSON.stringify(Object.keys(sound.binds)); @@ -969,6 +967,7 @@ this.patchLinks(sound.context.sourceNode, sound); } + setThing(args) { this.setThingNew(args) } setThingNew(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; From 66f7f37d1730e5bebe9e17e444567344a76fd68c Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sat, 7 Sep 2024 17:36:36 -0700 Subject: [PATCH 22/40] Tune-Shark-V3 -- Fixes --- extensions/SharkPool/Tune-Shark-V3.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index bd8fc15652..911d90ebf2 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.3.02 +// Version V.3.3.03 (function (Scratch) { "use strict"; @@ -19,7 +19,6 @@ "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiID48cGF0aCBkPSJtNjQuNTMzIDQyLjYxIDIuMDQyLjg1NWE1LjAyIDUuMDIgMCAwIDEgMi42OSA2LjU3bC0xLjM3IDMuMjc0YTUuMDIgNS4wMiAwIDAgMS02LjU3IDIuNjlsLTIuMDQyLS44NTVhMjUgMjUgMCAwIDEtNC4yOTUgNC4yNmwuODQgMi4wNWE1LjAyIDUuMDIgMCAwIDEtMi43NDIgNi41NDhsLTMuMjg1IDEuMzQ1YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtLjg0LTIuMDVhMjUgMjUgMCAwIDEtNi4wNDktLjAyMmwtLjg1NSAyLjA0MmE1LjAyIDUuMDIgMCAwIDEtNi41NyAyLjY5bC0zLjI3NC0xLjM3YTUuMDIgNS4wMiAwIDAgMS0yLjY5LTYuNTdsLjg1NS0yLjA0MmEyNSAyNSAwIDAgMS00LjI2LTQuMjk1bC0yLjA1Ljg0YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtMS4zNDUtMy4yODVhNS4wMiA1LjAyIDAgMCAxIDIuNzQyLTYuNTQ4bDIuMDUtLjg0YTI1IDI1IDAgMCAxIC4wMjItNi4wNDlsLTIuMDQyLS44NTVhNS4wMiA1LjAyIDAgMCAxLTIuNjktNi41N2wxLjM3LTMuMjc0YTUuMDIgNS4wMiAwIDAgMSA2LjU3LTIuNjlsMi4wNDIuODU1YTI1IDI1IDAgMCAxIDQuMjk1LTQuMjZsLS44NC0yLjA1YTUuMDIgNS4wMiAwIDAgMSAyLjc0Mi02LjU0OGwzLjI4NS0xLjM0NWE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsLjg0IDIuMDVhMjUgMjUgMCAwIDEgNi4wNDkuMDIybC44NTUtMi4wNDJhNS4wMiA1LjAyIDAgMCAxIDYuNTctMi42OWwzLjI3NCAxLjM3YTUuMDIgNS4wMiAwIDAgMSAyLjY5IDYuNTdsLS44NTUgMi4wNDJhMjUgMjUgMCAwIDEgNC4yNiA0LjI5NWwyLjA1LS44NGE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsMS4zNDUgMy4yODVhNS4wMiA1LjAyIDAgMCAxLTIuNzQyIDYuNTQ4bC0yLjA1Ljg0YTI1IDI1IDAgMCAxLS4wMjIgNi4wNDltLTM3LjQ5OC04LjMzOGMtMi44OCA2Ljg3Ny4zNiAxNC43ODcgNy4yMzcgMTcuNjY3czE0Ljc4Ny0uMzYgMTcuNjY3LTcuMjM3LS4zNi0xNC43ODctNy4yMzctMTcuNjY3LTE0Ljc4Ny4zNi0xNy42NjcgNy4yMzciIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjwvZz48L3N2Zz4="; const nobIconURI = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0wIDc4Ljk3NFYwaDc4Ljk3NHY3OC45NzR6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTM3Ljk2MSAxMC44NDdhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDRWNS42OTJjMC0xLjE2My45NDItMi4xMDUgMi4xMDQtMi4xMDVoMy4wNTJjMS4xNjIgMCAyLjEwNC45NDIgMi4xMDQgMi4xMDV2My4wNTFhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQgMi4xMDV6bTAgNjQuNTRhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDV2LTMuMDUxYzAtMS4xNjIuOTQyLTIuMTA1IDIuMTA0LTIuMTA1aDMuMDUyYzEuMTYyIDAgMi4xMDQuOTQzIDIuMTA0IDIuMTA1djMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA0IDIuMTA1em0yMC42OTgtNTcuMjNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6TTEzLjAyMyA2My43OTNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6bTU1LjEwNC0yNS44MzJjMC0xLjE2Mi45NDItMi4xMDQgMi4xMDQtMi4xMDRoMy4wNTFjMS4xNjMgMCAyLjEwNS45NDIgMi4xMDUgMi4xMDR2My4wNTJhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDUgMi4xMDRoLTMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA1LTIuMTA0em0tNjQuNTQgMGMwLTEuMTYyLjk0Mi0yLjEwNCAyLjEwNS0yLjEwNGgzLjA1MWMxLjE2MiAwIDIuMTA1Ljk0MiAyLjEwNSAyLjEwNHYzLjA1MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNSAyLjEwNEg1LjY5MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNS0yLjEwNHptNTcuMjMgMjAuNjk4YTIuMTA1IDIuMTA1IDAgMCAxIDIuOTc2IDBsMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMCAyLjk3NmwtMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEtMi45NzYgMGwtMi4xNTgtMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NnpNMTUuMTgxIDEzLjAyM2EyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDBsLTIuMTU4LTIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAtMi45NzZ6bTguNDE2IDEwLjQzYzcuNTQ2LTcuNTQ3IDE5LjA2NS04LjcwMiAyNy44MjctMy40NjUtLjEyNS4wOS0xMy4yNjQgMTcuODcyLTEyLjEzMyAxOS4wMDNsMS4wMzcgMS4wMzdjMS4xMyAxLjEzIDE4LjkxMy0xMi4wMDggMTkuMDAzLTEyLjEzMyA1LjIzNyA4Ljc2MiA0LjA4MiAyMC4yOC0zLjQ2NSAyNy44MjgtOC45MTEgOC45MS0yMy4zNTkgOC45MS0zMi4yNyAwLTguOTEtOC45MTEtOC45MS0yMy4zNTkgMC0zMi4yNyIgZmlsbD0iI2ZmZiIvPjwvZz48L3N2Zz4="; - const stopSign = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNCAxNCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6I2VjNTk1OTtzdHJva2U6I2I4NDg0ODtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIGQ9Ik00LjMuNWg1LjRsMy44IDMuOHY1LjRsLTMuOCAzLjhINC4zTC41IDkuN1Y0LjN6Ii8+PC9zdmc+"; const startFlag = @@ -829,13 +828,17 @@ } deleteSound(args) { + const sound = soundBank[args.NAME]; this.stopSound(args); + if (sound) { + sound.vol = 0; + sound.context.volume = 0; + } delete soundBank[args.NAME]; } deleteAllSounds() { - this.ctrlSounds({ CONTROL: "stop" }); - soundBank = {}; + for (let name in soundBank) this.deleteSound({ NAME: name }); } allSounds() { return JSON.stringify(Object.keys(soundBank)) } From 2ef7f46a5a4c5bfaa4c85b7e57c8a8404c72287a Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sat, 26 Oct 2024 16:59:42 -0700 Subject: [PATCH 23/40] Tune-Shark-V3 -- Current Time Rewrite --- extensions/SharkPool/Tune-Shark-V3.js | 350 ++++++++++++++------------ 1 file changed, 190 insertions(+), 160 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 911d90ebf2..586687da02 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.3.03 +// Version V.3.3.1 (function (Scratch) { "use strict"; @@ -15,14 +15,13 @@ const blockIconURI = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIj48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Im02MS4yMDIgNTMuODM2LS4wMDItLjAwM2MtLjA2OCAzLjcxLTMuMzM3IDcuMjgtOC4wMTYgOC40MTYtNS4zNTkgMS4zMDItMTAuNTM4LTEuMDgtMTEuNTY4LTUuMzJzMi40OC04LjczMyA3LjgzOS0xMC4wMzVjMi40NC0uNTkzIDQuODQtLjQyIDYuODMxLjM0MnYtMjAuNTZjLTE0Ljg1IDEuMDAzLTI2LjI5OCA1LjAwNi0yNi4yOTggNS4wMDZ2MjcuNjQ0Yy40OSA0LjAxNC0yLjkxNiA4LjA3OC03Ljk3IDkuMzA2LTUuMzU4IDEuMzAxLTEwLjUzNy0xLjA4MS0xMS41NjctNS4zMjFzMi40OC04LjczMyA3LjgzOC0xMC4wMzVjMi40ODYtLjYwNCA0LjkzMy0uNDE0IDYuOTQ2LjM4NGwuMDEyLTM2Ljk5OHMxNi4xOS03LjI5IDM1Ljk1NS02LjYzM3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; - const settingsIconURI = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiID48cGF0aCBkPSJtNjQuNTMzIDQyLjYxIDIuMDQyLjg1NWE1LjAyIDUuMDIgMCAwIDEgMi42OSA2LjU3bC0xLjM3IDMuMjc0YTUuMDIgNS4wMiAwIDAgMS02LjU3IDIuNjlsLTIuMDQyLS44NTVhMjUgMjUgMCAwIDEtNC4yOTUgNC4yNmwuODQgMi4wNWE1LjAyIDUuMDIgMCAwIDEtMi43NDIgNi41NDhsLTMuMjg1IDEuMzQ1YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtLjg0LTIuMDVhMjUgMjUgMCAwIDEtNi4wNDktLjAyMmwtLjg1NSAyLjA0MmE1LjAyIDUuMDIgMCAwIDEtNi41NyAyLjY5bC0zLjI3NC0xLjM3YTUuMDIgNS4wMiAwIDAgMS0yLjY5LTYuNTdsLjg1NS0yLjA0MmEyNSAyNSAwIDAgMS00LjI2LTQuMjk1bC0yLjA1Ljg0YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtMS4zNDUtMy4yODVhNS4wMiA1LjAyIDAgMCAxIDIuNzQyLTYuNTQ4bDIuMDUtLjg0YTI1IDI1IDAgMCAxIC4wMjItNi4wNDlsLTIuMDQyLS44NTVhNS4wMiA1LjAyIDAgMCAxLTIuNjktNi41N2wxLjM3LTMuMjc0YTUuMDIgNS4wMiAwIDAgMSA2LjU3LTIuNjlsMi4wNDIuODU1YTI1IDI1IDAgMCAxIDQuMjk1LTQuMjZsLS44NC0yLjA1YTUuMDIgNS4wMiAwIDAgMSAyLjc0Mi02LjU0OGwzLjI4NS0xLjM0NWE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsLjg0IDIuMDVhMjUgMjUgMCAwIDEgNi4wNDkuMDIybC44NTUtMi4wNDJhNS4wMiA1LjAyIDAgMCAxIDYuNTctMi42OWwzLjI3NCAxLjM3YTUuMDIgNS4wMiAwIDAgMSAyLjY5IDYuNTdsLS44NTUgMi4wNDJhMjUgMjUgMCAwIDEgNC4yNiA0LjI5NWwyLjA1LS44NGE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsMS4zNDUgMy4yODVhNS4wMiA1LjAyIDAgMCAxLTIuNzQyIDYuNTQ4bC0yLjA1Ljg0YTI1IDI1IDAgMCAxLS4wMjIgNi4wNDltLTM3LjQ5OC04LjMzOGMtMi44OCA2Ljg3Ny4zNiAxNC43ODcgNy4yMzcgMTcuNjY3czE0Ljc4Ny0uMzYgMTcuNjY3LTcuMjM3LS4zNi0xNC43ODctNy4yMzctMTcuNjY3LTE0Ljc4Ny4zNi0xNy42NjcgNy4yMzciIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjwvZz48L3N2Zz4="; - const nobIconURI = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0wIDc4Ljk3NFYwaDc4Ljk3NHY3OC45NzR6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTM3Ljk2MSAxMC44NDdhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDRWNS42OTJjMC0xLjE2My45NDItMi4xMDUgMi4xMDQtMi4xMDVoMy4wNTJjMS4xNjIgMCAyLjEwNC45NDIgMi4xMDQgMi4xMDV2My4wNTFhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQgMi4xMDV6bTAgNjQuNTRhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDV2LTMuMDUxYzAtMS4xNjIuOTQyLTIuMTA1IDIuMTA0LTIuMTA1aDMuMDUyYzEuMTYyIDAgMi4xMDQuOTQzIDIuMTA0IDIuMTA1djMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA0IDIuMTA1em0yMC42OTgtNTcuMjNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6TTEzLjAyMyA2My43OTNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6bTU1LjEwNC0yNS44MzJjMC0xLjE2Mi45NDItMi4xMDQgMi4xMDQtMi4xMDRoMy4wNTFjMS4xNjMgMCAyLjEwNS45NDIgMi4xMDUgMi4xMDR2My4wNTJhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDUgMi4xMDRoLTMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA1LTIuMTA0em0tNjQuNTQgMGMwLTEuMTYyLjk0Mi0yLjEwNCAyLjEwNS0yLjEwNGgzLjA1MWMxLjE2MiAwIDIuMTA1Ljk0MiAyLjEwNSAyLjEwNHYzLjA1MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNSAyLjEwNEg1LjY5MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNS0yLjEwNHptNTcuMjMgMjAuNjk4YTIuMTA1IDIuMTA1IDAgMCAxIDIuOTc2IDBsMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMCAyLjk3NmwtMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEtMi45NzYgMGwtMi4xNTgtMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NnpNMTUuMTgxIDEzLjAyM2EyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDBsLTIuMTU4LTIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAtMi45NzZ6bTguNDE2IDEwLjQzYzcuNTQ2LTcuNTQ3IDE5LjA2NS04LjcwMiAyNy44MjctMy40NjUtLjEyNS4wOS0xMy4yNjQgMTcuODcyLTEyLjEzMyAxOS4wMDNsMS4wMzcgMS4wMzdjMS4xMyAxLjEzIDE4LjkxMy0xMi4wMDggMTkuMDAzLTEyLjEzMyA1LjIzNyA4Ljc2MiA0LjA4MiAyMC4yOC0zLjQ2NSAyNy44MjgtOC45MTEgOC45MS0yMy4zNTkgOC45MS0zMi4yNyAwLTguOTEtOC45MTEtOC45MS0yMy4zNTkgMC0zMi4yNyIgZmlsbD0iI2ZmZiIvPjwvZz48L3N2Zz4="; - const stopSign = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNCAxNCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6I2VjNTk1OTtzdHJva2U6I2I4NDg0ODtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIGQ9Ik00LjMuNWg1LjRsMy44IDMuOHY1LjRsLTMuOCAzLjhINC4zTC41IDkuN1Y0LjN6Ii8+PC9zdmc+"; - const startFlag = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48cGF0aCBkPSJNLjc1IDJhNi40NCA2LjQ0IDAgMCAxIDcuNjkgMGgwYTYuNDQgNi40NCAwIDAgMCA3LjY5IDB2MTAuNGE2LjQ0IDYuNDQgMCAwIDEtNy42OSAwaDBhNi40NCA2LjQ0IDAgMCAwLTcuNjkgMCIgc3R5bGU9ImZpbGw6IzRjYmY1NjtzdHJva2U6IzQ1OTkzZDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQiLz48cGF0aCBzdHlsZT0iZmlsbDojNGNiZjU2O3N0cm9rZTojNDU5OTNkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MS41cHgiIGQ9Ik0uNzUgMTYuNzV2LTE2Ii8+PC9zdmc+"; + const extraIcons = { + set: "B4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiID48cGF0aCBkPSJtNjQuNTMzIDQyLjYxIDIuMDQyLjg1NWE1LjAyIDUuMDIgMCAwIDEgMi42OSA2LjU3bC0xLjM3IDMuMjc0YTUuMDIgNS4wMiAwIDAgMS02LjU3IDIuNjlsLTIuMDQyLS44NTVhMjUgMjUgMCAwIDEtNC4yOTUgNC4yNmwuODQgMi4wNWE1LjAyIDUuMDIgMCAwIDEtMi43NDIgNi41NDhsLTMuMjg1IDEuMzQ1YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtLjg0LTIuMDVhMjUgMjUgMCAwIDEtNi4wNDktLjAyMmwtLjg1NSAyLjA0MmE1LjAyIDUuMDIgMCAwIDEtNi41NyAyLjY5bC0zLjI3NC0xLjM3YTUuMDIgNS4wMiAwIDAgMS0yLjY5LTYuNTdsLjg1NS0yLjA0MmEyNSAyNSAwIDAgMS00LjI2LTQuMjk1bC0yLjA1Ljg0YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtMS4zNDUtMy4yODVhNS4wMiA1LjAyIDAgMCAxIDIuNzQyLTYuNTQ4bDIuMDUtLjg0YTI1IDI1IDAgMCAxIC4wMjItNi4wNDlsLTIuMDQyLS44NTVhNS4wMiA1LjAyIDAgMCAxLTIuNjktNi41N2wxLjM3LTMuMjc0YTUuMDIgNS4wMiAwIDAgMSA2LjU3LTIuNjlsMi4wNDIuODU1YTI1IDI1IDAgMCAxIDQuMjk1LTQuMjZsLS44NC0yLjA1YTUuMDIgNS4wMiAwIDAgMSAyLjc0Mi02LjU0OGwzLjI4NS0xLjM0NWE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsLjg0IDIuMDVhMjUgMjUgMCAwIDEgNi4wNDkuMDIybC44NTUtMi4wNDJhNS4wMiA1LjAyIDAgMCAxIDYuNTctMi42OWwzLjI3NCAxLjM3YTUuMDIgNS4wMiAwIDAgMSAyLjY5IDYuNTdsLS44NTUgMi4wNDJhMjUgMjUgMCAwIDEgNC4yNiA0LjI5NWwyLjA1LS44NGE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsMS4zNDUgMy4yODVhNS4wMiA1LjAyIDAgMCAxLTIuNzQyIDYuNTQ4bC0yLjA1Ljg0YTI1IDI1IDAgMCAxLS4wMjIgNi4wNDltLTM3LjQ5OC04LjMzOGMtMi44OCA2Ljg3Ny4zNiAxNC43ODcgNy4yMzcgMTcuNjY3czE0Ljc4Ny0uMzYgMTcuNjY3LTcuMjM3LS4zNi0xNC43ODctNy4yMzctMTcuNjY3LTE0Ljc4Ny4zNi0xNy42NjcgNy4yMzciIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjwvZz48L3N2Zz4=", + nob: "B4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0wIDc4Ljk3NFYwaDc4Ljk3NHY3OC45NzR6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTM3Ljk2MSAxMC44NDdhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDRWNS42OTJjMC0xLjE2My45NDItMi4xMDUgMi4xMDQtMi4xMDVoMy4wNTJjMS4xNjIgMCAyLjEwNC45NDIgMi4xMDQgMi4xMDV2My4wNTFhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQgMi4xMDV6bTAgNjQuNTRhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDV2LTMuMDUxYzAtMS4xNjIuOTQyLTIuMTA1IDIuMTA0LTIuMTA1aDMuMDUyYzEuMTYyIDAgMi4xMDQuOTQzIDIuMTA0IDIuMTA1djMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA0IDIuMTA1em0yMC42OTgtNTcuMjNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6TTEzLjAyMyA2My43OTNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6bTU1LjEwNC0yNS44MzJjMC0xLjE2Mi45NDItMi4xMDQgMi4xMDQtMi4xMDRoMy4wNTFjMS4xNjMgMCAyLjEwNS45NDIgMi4xMDUgMi4xMDR2My4wNTJhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDUgMi4xMDRoLTMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA1LTIuMTA0em0tNjQuNTQgMGMwLTEuMTYyLjk0Mi0yLjEwNCAyLjEwNS0yLjEwNGgzLjA1MWMxLjE2MiAwIDIuMTA1Ljk0MiAyLjEwNSAyLjEwNHYzLjA1MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNSAyLjEwNEg1LjY5MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNS0yLjEwNHptNTcuMjMgMjAuNjk4YTIuMTA1IDIuMTA1IDAgMCAxIDIuOTc2IDBsMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMCAyLjk3NmwtMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEtMi45NzYgMGwtMi4xNTgtMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NnpNMTUuMTgxIDEzLjAyM2EyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDBsLTIuMTU4LTIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAtMi45NzZ6bTguNDE2IDEwLjQzYzcuNTQ2LTcuNTQ3IDE5LjA2NS04LjcwMiAyNy44MjctMy40NjUtLjEyNS4wOS0xMy4yNjQgMTcuODcyLTEyLjEzMyAxOS4wMDNsMS4wMzcgMS4wMzdjMS4xMyAxLjEzIDE4LjkxMy0xMi4wMDggMTkuMDAzLTEyLjEzMyA1LjIzNyA4Ljc2MiA0LjA4MiAyMC4yOC0zLjQ2NSAyNy44MjgtOC45MTEgOC45MS0yMy4zNTkgOC45MS0zMi4yNyAwLTguOTEtOC45MTEtOC45MS0yMy4zNTkgMC0zMi4yNyIgZmlsbD0iI2ZmZiIvPjwvZz48L3N2Zz4=", + flag: "B4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48cGF0aCBkPSJNLjc1IDJhNi40NCA2LjQ0IDAgMCAxIDcuNjkgMGgwYTYuNDQgNi40NCAwIDAgMCA3LjY5IDB2MTAuNGE2LjQ0IDYuNDQgMCAwIDEtNy42OSAwaDBhNi40NCA2LjQ0IDAgMCAwLTcuNjkgMCIgc3R5bGU9ImZpbGw6IzRjYmY1NjtzdHJva2U6IzQ1OTkzZDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQiLz48cGF0aCBzdHlsZT0iZmlsbDojNGNiZjU2O3N0cm9rZTojNDU5OTNkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MS41cHgiIGQ9Ik0uNzUgMTYuNzV2LTE2Ii8+PC9zdmc+", + stop: "B4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNCAxNCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6I2VjNTk1OTtzdHJva2U6I2I4NDg0ODtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIGQ9Ik00LjMuNWg1LjRsMy44IDMuOHY1LjRsLTMuOCAzLjhINC4zTC41IDkuN1Y0LjN6Ii8+PC9zdmc+" + }; + for (const key in extraIcons) extraIcons[key] = "data:image/svg+xml;base64,PHN2Zy" + extraIcons[key]; // Modified Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) // uses MIT License @@ -32,13 +31,18 @@ document.body.appendChild(scriptElement); /* global Pizzicato */ - const vm = Scratch.vm; + const { vm, Cast } = Scratch; const runtime = vm.runtime; const scratchAudio = runtime.audioEngine; - const allSingleEffects = [ + + const simpleEffects = [ "pitch", "detune", "speed", "pan", "gain", "distortion", "attack", "release" ]; + const complexEffects = [ + "reverb", "delay", "tremolo", "fuzz", "bitcrush", + "highpass", "lowpass", "flanger", "compressor", "equalizer" + ]; let soundBank = {}; let settings = { flagCtrl : false, canSave : false }; @@ -72,10 +76,10 @@ class SPtuneShark3 { constructor() { runtime.on("PROJECT_START", () => { - if (settings.flagCtrl) this.ctrlSounds({ CONTROL : "stop" }); + if (settings.flagCtrl) this.ctrlSounds({ CONTROL: "stop" }); }); runtime.on("PROJECT_STOP_ALL", () => { - if (settings.flagCtrl) this.ctrlSounds({ CONTROL : "stop" }); + if (settings.flagCtrl) this.ctrlSounds({ CONTROL: "stop" }); }); runtime.on("BEFORE_EXECUTE", () => { const projectVal = scratchAudio.inputNode.gain.value; @@ -115,7 +119,7 @@ opcode: "importURL", blockType: Scratch.BlockType.COMMAND, text: "import sound from URL [URL] named [NAME]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } @@ -125,7 +129,7 @@ opcode: "importMenu", blockType: Scratch.BlockType.COMMAND, text: "import sound [SOUND] named [NAME]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { SOUND: { type: Scratch.ArgumentType.SOUND }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } @@ -135,7 +139,7 @@ opcode: "convertSound", blockType: Scratch.BlockType.COMMAND, text: "convert sound [NAME1] from URL to URI and save to [NAME2]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { NAME1: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, NAME2: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound2" } @@ -145,7 +149,7 @@ opcode: "bindSound", blockType: Scratch.BlockType.COMMAND, text: "[TYPE] sound [NAME2] and sound [NAME]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { TYPE: { type: Scratch.ArgumentType.STRING, menu: "bindMenu" }, NAME2: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, @@ -156,7 +160,7 @@ opcode: "save2Project", blockType: Scratch.BlockType.COMMAND, text: "[SAVE] all sounds to project", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { SAVE: { type: Scratch.ArgumentType.STRING, menu: "saveMenu" } }, @@ -220,10 +224,10 @@ opcode: "enableControl", blockType: Scratch.BlockType.COMMAND, text: "toggle sound link to [GO] [STOP] [ON_OFF]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { - GO: { type: Scratch.ArgumentType.IMAGE, dataURI: startFlag }, - STOP: { type: Scratch.ArgumentType.IMAGE, dataURI: stopSign }, + GO: { type: Scratch.ArgumentType.IMAGE, dataURI: extraIcons.flag }, + STOP: { type: Scratch.ArgumentType.IMAGE, dataURI: extraIcons.stop }, ON_OFF: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } } }, @@ -231,27 +235,27 @@ opcode: "toggleOverlap", blockType: Scratch.BlockType.COMMAND, text: "toggle sound [NAME] overlapping [TYPE]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } }, }, { - opcode: "toggleLoop", + opcode: "toggleReverse", blockType: Scratch.BlockType.COMMAND, - text: "toggle sound [NAME] looping [TYPE]", - blockIconURI: settingsIconURI, + text: "toggle sound [NAME] reverse mode [TYPE]", + blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } }, }, { - opcode: "toggleReverse", + opcode: "toggleLoop", blockType: Scratch.BlockType.COMMAND, - text: "toggle sound [NAME] reverse mode [TYPE]", - blockIconURI: settingsIconURI, + text: "toggle sound [NAME] looping [TYPE]", + blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } @@ -261,7 +265,7 @@ opcode: "loopParams", blockType: Scratch.BlockType.COMMAND, text: "sound [NAME] loop start [START] end [END]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, START: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, @@ -273,7 +277,7 @@ opcode: "deleteSound", blockType: Scratch.BlockType.COMMAND, text: "delete sound [NAME]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, @@ -282,26 +286,37 @@ opcode: "deleteAllSounds", blockType: Scratch.BlockType.COMMAND, text: "delete all sounds", - blockIconURI: settingsIconURI + blockIconURI: extraIcons.set }, { opcode: "allSounds", blockType: Scratch.BlockType.REPORTER, text: "all sounds", - blockIconURI: settingsIconURI + blockIconURI: extraIcons.set }, { opcode: "allPlaySounds", blockType: Scratch.BlockType.REPORTER, text: "all playing sounds", - blockIconURI: settingsIconURI + blockIconURI: extraIcons.set }, "---", + { + opcode: "whenSound", + blockType: Scratch.BlockType.HAT, + text: "when sound [NAME] [CONTROL]", + isEdgeActivated: false, + blockIconURI: extraIcons.set, + arguments: { + NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + CONTROL: { type: Scratch.ArgumentType.STRING, menu: "hatPlayer" } + }, + }, { opcode: "soundCheck", blockType: Scratch.BlockType.BOOLEAN, text: "sound [NAME] [CONTROL] ?", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, CONTROL: { type: Scratch.ArgumentType.STRING, menu: "soundBools" } @@ -311,7 +326,7 @@ opcode: "soundProperty", blockType: Scratch.BlockType.REPORTER, text: "[PROP] of sound [NAME]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, PROP: { type: Scratch.ArgumentType.STRING, menu: "soundProps" } @@ -321,7 +336,7 @@ opcode: "getLoudTime", blockType: Scratch.BlockType.REPORTER, text: "[TYPE] of sound [NAME] at time [TIME]", - blockIconURI: settingsIconURI, + blockIconURI: extraIcons.set, arguments: { TYPE: { type: Scratch.ArgumentType.STRING, menu: "loudProps" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, @@ -333,7 +348,7 @@ opcode: "setVol", blockType: Scratch.BlockType.COMMAND, text: "set volume of sound [NAME] to [NUM]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } @@ -343,23 +358,18 @@ opcode: "resetEffect", blockType: Scratch.BlockType.COMMAND, text: "reset [EFFECT] of sound [NAME]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { EFFECT: { type: Scratch.ArgumentType.STRING, menu: "effectMenu" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } }, }, "---", - { - opcode: "setThing", blockType: Scratch.BlockType.COMMAND, hideFromPalette: true, // deprecated - text: "set [TYPE] of sound [NAME] to [VALUE]", blockIconURI: nobIconURI, - arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "singleEffects" }, VALUE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } }, - }, { opcode: "setThingNew", blockType: Scratch.BlockType.COMMAND, text: "set [TYPE] of sound [NAME] to [VALUE]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "singleEffectNew" }, @@ -370,7 +380,7 @@ opcode: "setReverb", blockType: Scratch.BlockType.COMMAND, text: "set reverb of sound [NAME] to time [TIME] decay [DECAY] mix [MIX]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, @@ -382,7 +392,7 @@ opcode: "setDelay", blockType: Scratch.BlockType.COMMAND, text: "set delay of sound [NAME] to time [TIME] feedback [FEED] mix [MIX]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, FEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, @@ -394,7 +404,7 @@ opcode: "setTremolo", blockType: Scratch.BlockType.COMMAND, text: "set tremolo of sound [NAME] to speed [SPEED] depth [DEPTH] mix [MIX]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, SPEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 35 }, @@ -407,7 +417,7 @@ opcode: "setFuzz", blockType: Scratch.BlockType.COMMAND, text: "set fuzz of sound [NAME] to low [LOW] med-low [MED1] med-high [MED2] high [HIGH] mix [MIX]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, LOW: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, @@ -421,10 +431,10 @@ opcode: "setBitcrush", blockType: Scratch.BlockType.COMMAND, text: "set bitcrush of sound [NAME] bits [BITS] freq [FREQ]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - BITS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 }, + BITS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 65 }, FREQ: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60000 } }, }, @@ -432,7 +442,7 @@ opcode: "setPass", blockType: Scratch.BlockType.COMMAND, text: "set [TYPE] of sound [NAME] to frequency [FREQ] peak [PEAK]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "typePass" }, @@ -444,7 +454,7 @@ opcode: "setFlanger", blockType: Scratch.BlockType.COMMAND, text: "set flanger of sound [NAME] to time [TIME] speed [SPEED] depth [DEPTH] feed [FEED] mix [MIX]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 45 }, @@ -458,7 +468,7 @@ opcode: "setCompress", blockType: Scratch.BlockType.COMMAND, text: "set compressor of sound [NAME] to threshold [THRESH] knee [KNEE] attack [ATTACK] release [RELEASE] ratio [RATIO]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, THRESH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, @@ -472,7 +482,7 @@ opcode: "setEqualize", blockType: Scratch.BlockType.COMMAND, text: "set equalizer of sound [NAME] to gain low [LOW] mid [MID] high [HIGH] cutoff low [CUT_LOW] cutoff high [CUT_HIGH]", - blockIconURI: nobIconURI, + blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, LOW: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, @@ -484,22 +494,20 @@ } ], menus: { - singleEffects: allSingleEffects, // deprecated saveMenu: ["save", "dont save"], un_pauseMenu: ["pause", "unpause"], playMenu: ["start", "stop", "pause", "unpause"], + hatPlayer: ["starts", "stops"], toggleMenu: ["on", "off"], bindMenu: ["bind", "unBind"], loudProps: ["loudness", "raw noise", "tone"], typePass: ["highpass", "lowpass"], - singleEffectNew: { acceptReporters: true, items: allSingleEffects }, + singleEffectNew: { acceptReporters: true, items: simpleEffects }, soundProps: { acceptReporters: true, items: [ - "length", "current time", "estimated bpm", "source", "binds", "volume", "pitch", "detune", - "speed", "pan", "gain", "distortion", "attack", "release", "reverb", "delay", "tremolo", - "fuzz", "bitcrush", "highpass", "lowpass", "flanger", "compressor", "equalizer" - ] + "length", "current time", "estimated bpm", "source", "binds", "volume", + ].concat(simpleEffects, complexEffects) }, soundBools: { acceptReporters: true, @@ -507,35 +515,13 @@ }, effectMenu: { acceptReporters: true, - items: [ - "all effects", "pitch", "detune", "speed", "pan", "gain", "distortion", - "attack", "release", "reverb", "delay", "tremolo", "fuzz", "bitcrush", - "highpass", "lowpass", "flanger", "compressor", "equalizer" - ] + items: ["all effects"].concat(simpleEffects, complexEffects) } }, }; } // Helper Funcs - getSoundIndex(name, target) { - const sounds = target.sounds; - return sounds.indexOf(sounds.filter((i) => { return i.name === name })[0]); - } - - currentTime(sound, ctx, src) { - if (!ctx.playing && !ctx.paused) return 0; - let leng = ctx.loop && sound.loopParm[1] ? sound.loopParm[1] : src.buffer.duration; - leng = this.modTime(leng, sound); - const loopStart = sound.loopParm[0]; - const now = ctx.paused ? sound.pauseTime : src.context.currentTime; - let time = Math.abs(ctx.lastTimePlayed - now); - if (ctx.loop) return (Math.max(0, time % (leng - loopStart)) + loopStart); - return Math.min(leng, Math.max(0, time)); - } - - modTime(number, opts) { return number / (opts.pitch * opts.speed * ((opts.detune / 1000) + 1)) } - updateEffect(effect, sound, name, args) { delete args.NAME; delete args.TYPE; effect.arguments = args; // Match Original Values, not Converted @@ -558,14 +544,14 @@ } case "DISTORTION": { return thisEffect.gain = options.gain } case "BITCRUSH": { - thisEffect.frequency = Math.max(30000, Scratch.Cast.toNumber(args.FREQ)); - thisEffect.bits = Math.max(10, Scratch.Cast.toNumber(args.BITS)) / 10; + thisEffect.frequency = Math.max(30000, Cast.toNumber(args.FREQ)); + thisEffect.bits = Math.max(10, Cast.toNumber(args.BITS)) / 10; return; } case "LOWPASS": case "HIGHPASS": { - const freq = Scratch.Cast.toNumber(args.FREQ); - const peak = Scratch.Cast.toNumber(args.PEAK) / 5; + const freq = Cast.toNumber(args.FREQ); + const peak = Cast.toNumber(args.PEAK) / 5; thisEffect.filterNode.frequency.value = freq; thisEffect.inputNode.frequency.value = freq; thisEffect.frequency = freq; @@ -577,9 +563,9 @@ case "COMPRESSOR": { const node = thisEffect.compressorNode; const values = { - threshold: Math.min(0, Math.max(-100, Scratch.Cast.toNumber(args.THRESH) * -1)), - ratio: Scratch.Cast.toNumber(args.RATIO) / 5, attack: Math.min(0, Math.max(1, Scratch.Cast.toNumber(args.ATTACK) / 100)), - release: Math.min(0, Math.max(1, Scratch.Cast.toNumber(args.RELEASE) / 100)), knee: Scratch.Cast.toNumber(args.KNEE) / 2.5 + threshold: Math.min(0, Math.max(-100, Cast.toNumber(args.THRESH) * -1)), + ratio: Cast.toNumber(args.RATIO) / 5, attack: Math.min(0, Math.max(1, Cast.toNumber(args.ATTACK) / 100)), + release: Math.min(0, Math.max(1, Cast.toNumber(args.RELEASE) / 100)), knee: Cast.toNumber(args.KNEE) / 2.5 }; Object.keys(values).forEach(key => { node[key].value = values[key] }); return; @@ -591,8 +577,7 @@ getBPM(data, sampleRate) { const peaks = []; - let lastPeakIndex = 0; - let max = 0.1; + let lastPeakIndex = 0, max = 0.1; for (let i = 0; i < data.length; i++) { if (data[i] > max) max = data[i] } for (let i = 0; i < data.length; i++) { if (data[i] > max - 0.1 && i - lastPeakIndex > sampleRate / 4) { @@ -601,7 +586,7 @@ } } const intervals = []; - for (let i = 1; i < peaks.length; i++) { intervals.push(peaks[i] - peaks[i - 1]) } + for (let i = 1; i < peaks.length; i++) intervals.push(peaks[i] - peaks[i - 1]); const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length; const value = Math.round((sampleRate / avgInterval) * 60); return isNaN(value) ? 0 : value; @@ -615,7 +600,7 @@ soundBank[newName] = { ...sound, context: clone, name: newName, loopParm: [0, 0], pauseTime: 0, - overlap: false, overlays: [], isBind: false, binds: {} + rateChange: [0,0], overlap: false, overlays: [], isBind: false, binds: {} }; clone.play(); clone.sourceNode.playbackRate.value = con.pitch; @@ -623,6 +608,7 @@ con.overlays.push(clone); clone.on("end", () => { delete soundBank[newName] }); } else { + con.rateChange = [0,0]; sound.play(0, sound.loop ? con.loopParm[0] : atTime); const srcNode = sound.sourceNode; this.patchLinks(srcNode, con); @@ -636,6 +622,7 @@ } if (sound.loop) this.loopParams({ NAME : con.name , START : con.loopParm[0], END : con.loopParm[1] }); } + this.startHats({ name: con.name, type: "starts" }); } catch { console.warn("Audio has not Loaded Yet, Ignore Next Error"); sound.stop(); // Reset @@ -647,12 +634,15 @@ const src = ctx.sourceNode; if (type === "stop") { ctx.stop(); - for (let i = 0; i < sound.overlays.length; i++) { sound.overlays[i].stop() } + for (let i = 0; i < sound.overlays.length; i++) sound.overlays[i].stop(); + this.startHats({ name: sound.name, type: "stops" }); } else if (type === "pause") { sound.pauseTime = src.context.currentTime; ctx.pause(); - for (let i = 0; i < sound.overlays.length; i++) { sound.overlays[i].pause() } + for (let i = 0; i < sound.overlays.length; i++) sound.overlays[i].pause(); + this.startHats({ name: sound.name, type: "stops" }); } else { + this.startHats({ name: sound.name, type: "starts" }); if (type === "play") ctx.play(); else { if (!ctx.paused) return; @@ -669,13 +659,44 @@ } } - patchLinks(src, sound) { + currentTime(sound, ctx, src) { + if (!ctx.playing && !ctx.paused) return 0; + const rate = sound.pitch * sound.speed * Math.pow(2, sound.detune / 1200); + const leng = ctx.loop && sound.loopParm[1] ? sound.loopParm[1] : src.buffer.duration; + const loopStart = sound.loopParm[0]; + const now = ctx.paused ? sound.pauseTime : src.context.currentTime; + let time = now - ctx.lastTimePlayed; + time = sound.rateChange[1] + ((time - sound.rateChange[0]) * rate); + if (ctx.loop) return Math.max(0, time % (leng - loopStart) + 0.00001) + loopStart; + return Math.min(leng, Math.max(0, time)); + } + + patchLinks(src, sound, optShifters) { + if (optShifters) { + const ctx = sound.context; + if ( + optShifters[0] !== sound.pitch || optShifters[1] !== sound.detune || optShifters[2] !== sound.speed + ) sound.rateChange = [ + this.currentTime(sound, ctx, src), + (ctx.paused ? sound.pauseTime : src.context.currentTime) - ctx.lastTimePlayed + ]; + } src.playbackRate.value = sound.pitch; src.detune.value = sound.detune; src.gainSuccessor.gain.value = sound.gain; if (src.loop) this.loopParams({ NAME : sound.name , START : sound.loopParm[0], END : sound.loopParm[1] }); } + startHats(data) { + let newThreads = []; + runtime.allScriptsByOpcodeDo("SPtuneShark3_whenSound", (script, target) => { + const thread = runtime._pushThread(script.blockId, target); + thread.SPts3Data = data; + newThreads.push(thread); + }); + return newThreads; + } + // Block Funcs importURL(args, util) { return new Promise((resolve) => { @@ -687,12 +708,13 @@ engine.attack = 0; soundBank[args.NAME] = { context: engine, name: args.NAME, src: args.URL, effects: {}, pauseTime: 0, - loaded: true, reversed: false, vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, - loopParm: [0, 0], overlap: false, overlays: [], isBind: false, binds: {} + rateChange: [0,0], loaded: true, reversed: false, vol: 100, gain: 1, pitch: 1, + detune: 0, speed: 1, loopParm: [0, 0], overlap: false, overlays: [], + isBind: false, binds: {} }; resolve(); } catch { - // File is Corrupted / Doesnt Exist / is a unedited Scratch Sound + // File is Corrupted/Doesnt Exist/is a unedited Scratch Sound alert("Tune Shark V3 Cant Import this Sound, File may be Corrupted or Non-Existent"); resolve(); } @@ -701,15 +723,14 @@ } async importMenu(args, util) { - const target = util.target.sprite; - const index = this.getSoundIndex(args.SOUND, target); - if (index < 0) return; if (runtime.isPackaged) { alert(`For "Import Scratch Sound" (Tune Shark) to Work, Disable "Remove raw asset data after loading to save RAM" under advanced settings in the packager.`); return; } - const sound = target.sounds[index].asset.encodeDataURI(); - await this.importURL({ ...args, URL: sound }, util); + const name = Cast.toString(args.SOUND); + const target = util.target.sprite; + const sound = target.sounds.filter((i) => { return i.name === name })[0]; + if (sound) await this.importURL({ ...args, URL: sound.asset.encodeDataURI() }, util); } async convertSound(args, util) { @@ -749,15 +770,15 @@ startSoundAt(args) { const sound = soundBank[args.NAME]; - const time = Scratch.Cast.toNumber(args.TIME); + const time = Cast.toNumber(args.TIME); if (sound !== undefined) this.play(sound.context, time, sound); } async playAndStop(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - const time = Scratch.Cast.toNumber(args.TIME); - const max = Scratch.Cast.toNumber(args.MAX); + const time = Cast.toNumber(args.TIME); + const max = Cast.toNumber(args.MAX); this.play(sound.context, time, sound); await new Promise((resolve) => { setTimeout(() => { @@ -809,7 +830,7 @@ this.typeOverlay(sound, "stop"); const node = sound.context.sourceNode; const reverseBuffer = (buffer) => { - for (let i = 0; i < buffer.numberOfChannels; i++) { buffer.getChannelData(i).reverse() } + for (let i = 0; i < buffer.numberOfChannels; i++) buffer.getChannelData(i).reverse(); return buffer; } const bufferSource = node.context.createBufferSource(); @@ -822,8 +843,8 @@ if (sound === undefined) return; sound.context.loop = true; const srcNode = sound.context.sourceNode; - srcNode.loopStart = Scratch.Cast.toNumber(args.START); - srcNode.loopEnd = Scratch.Cast.toNumber(args.END); + srcNode.loopStart = Cast.toNumber(args.START); + srcNode.loopEnd = Cast.toNumber(args.END); sound.loopParm = [srcNode.loopStart, srcNode.loopEnd]; } @@ -838,7 +859,7 @@ } deleteAllSounds() { - for (let name in soundBank) this.deleteSound({ NAME: name }); + for (let name in soundBank) this.deleteSound({ NAME: name }) } allSounds() { return JSON.stringify(Object.keys(soundBank)) } @@ -851,6 +872,11 @@ return JSON.stringify(players); } + whenSound(args, util) { + const data = util.thread.SPts3Data ?? {}; + return args.CONTROL === data.type && args.NAME === data.name; + } + soundCheck(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return false; @@ -871,7 +897,7 @@ if (sound === undefined) return 0; const src = sound.context.sourceNode; switch (args.PROP) { - case "length": return this.modTime(src.buffer.duration, sound); + case "length": return src.buffer.duration; case "current time": return this.currentTime(sound, sound.context, src); case "estimated bpm": return this.getBPM(src.buffer.getChannelData(0), src.buffer.sampleRate); case "source": return sound.src; @@ -896,7 +922,7 @@ getLoudTime(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return 0; - const time = Scratch.Cast.toNumber(args.TIME); + const time = Cast.toNumber(args.TIME); const duration = sound.context.sourceNode.buffer.duration; if (time < 0 || time > duration) return 0; const audioBuffer = sound.context.sourceNode.buffer; @@ -934,7 +960,7 @@ if (bestTau > 0) return sampleRate / bestTau; return 0; } else { - for (let i = startSample; i < endSample; i++) { sample += channelData[i] * channelData[i] } + for (let i = startSample; i < endSample; i++) sample += channelData[i] * channelData[i]; const rms = Math.sqrt(sample / (endSample - startSample)); const dB = 20 * Math.log10(rms); sample = Math.min(Math.max((dB + 50) / 50, 0), 1) * 100; @@ -945,36 +971,42 @@ setVol(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - sound.vol = Math.max(0, Scratch.Cast.toNumber(args.NUM)); + sound.vol = Math.max(0, Cast.toNumber(args.NUM)); } resetEffect(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - if (args.EFFECT === "all effects") { + const ctx = sound.context; + const effect = Cast.toString(args.EFFECT); + const name = effect.toUpperCase(); + const ogShifters = [sound.pitch, sound.detune, sound.speed]; + if (effect === "pitch") sound.pitch = 1; + else if (effect === "detune") sound.detune = 0; + else if (effect === "speed") sound.speed = 1; + else if (effect === "gain") sound.gain = 1; + else if (effect === "attack") sound.context.attack = 0; + else if (effect === "release") sound.context.release = 0; + else if (effect === "all effects") { + sound.pitch = 1; sound.detune = 0; sound.speed = 1; + sound.gain = 1; sound.context.attack = 0; sound.context.release = 0; const effects = sound.effects; - Object.keys(effects).forEach(key => { sound.context.removeEffect(effects[key]) }); + Object.keys(effects).forEach(key => { ctx.removeEffect(effects[key]) }); sound.effects = {}; } - if (args.EFFECT === "all effects" || args.EFFECT === "pitch") sound.pitch = 1; - if (args.EFFECT === "all effects" || args.EFFECT === "detune") sound.detune = 0; - if (args.EFFECT === "all effects" || args.EFFECT === "speed") sound.speed = 1; - if (args.EFFECT === "all effects" || args.EFFECT === "gain") sound.gain = 1; - if (args.EFFECT === "all effects" || args.EFFECT === "attack") sound.context.attack = 0; - if (args.EFFECT === "all effects" || args.EFFECT === "release") sound.context.release = 0; - const name = args.EFFECT.toUpperCase(); if (sound.effects[name] !== undefined) { - sound.context.removeEffect(sound.effects[name]); + ctx.removeEffect(sound.effects[name]); delete sound.effects[name]; } - this.patchLinks(sound.context.sourceNode, sound); + this.patchLinks(ctx.sourceNode, sound, ogShifters); } - setThing(args) { this.setThingNew(args) } setThingNew(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - const value = Scratch.Cast.toNumber(args.VALUE) / 100; + const ctx = sound.context; + const value = Cast.toNumber(args.VALUE) / 100; + const ogShifters = [sound.pitch, sound.detune, sound.speed]; if (args.TYPE === "pitch") sound.pitch = Math.max(0, value + 1); else if (args.TYPE === "detune") sound.detune = value * 1000; else if (args.TYPE === "speed") sound.speed = Math.max(0, value); @@ -988,15 +1020,15 @@ const distort = new Pizzicato.Effects.Distortion({ gain: value }); return this.updateEffect(distort, sound, "DISTORTION", args); } - this.patchLinks(sound.context.sourceNode, sound); + this.patchLinks(ctx.sourceNode, sound, ogShifters); } setReverb(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; const reverb = new Pizzicato.Effects.Reverb({ - time: Scratch.Cast.toNumber(args.TIME) / 10, decay: Scratch.Cast.toNumber(args.DECAY) / 10, - mix: Scratch.Cast.toNumber(args.MIX) / 100, + time: Cast.toNumber(args.TIME) / 10, decay: Cast.toNumber(args.DECAY) / 10, + mix: Cast.toNumber(args.MIX) / 100, }); this.updateEffect(reverb, sound, "REVERB", args); } @@ -1005,8 +1037,8 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; const delay = new Pizzicato.Effects.Delay({ - time: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.TIME) / 100)), - decay: Scratch.Cast.toNumber(args.FEED) / 100, mix: Scratch.Cast.toNumber(args.MIX) / 100, + time: Math.min(1, Math.max(0, Cast.toNumber(args.TIME) / 100)), + decay: Cast.toNumber(args.FEED) / 100, mix: Cast.toNumber(args.MIX) / 100, }); this.updateEffect(delay, sound, "DELAY", args); } @@ -1015,11 +1047,11 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; const fuzz = new Pizzicato.Effects.Quadrafuzz({ - lowGain: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.LOW) / 100)), - midLowGain: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.MED1) / 100)), - midHighGain: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.MED2) / 100)), - highGain: Math.min(1, Math.max(0, Scratch.Cast.toNumber(args.HIGH) / 100)), - mix: Scratch.Cast.toNumber(args.MIX) / 100 + lowGain: Math.min(1, Math.max(0, Cast.toNumber(args.LOW) / 100)), + midLowGain: Math.min(1, Math.max(0, Cast.toNumber(args.MED1) / 100)), + midHighGain: Math.min(1, Math.max(0, Cast.toNumber(args.MED2) / 100)), + highGain: Math.min(1, Math.max(0, Cast.toNumber(args.HIGH) / 100)), + mix: Cast.toNumber(args.MIX) / 100 }); this.updateEffect(fuzz, sound, "FUZZ", args); } @@ -1027,14 +1059,12 @@ setBitcrush(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - /* NOTE: Bitcrusher uses "ScriptProcessorNode" wich is deprecated. - From what Ive tested and read online, ending support for this doesnt seem to be - going anywhere. If a problem emerges we need to try and find a way to - use "AudioWorkletNodes" instead which is more complicated to use. + /* NOTE: Bitcrusher uses "ScriptProcessorNode" + despite being "deprecated" they are still widely used */ const bitcrush = new Pizzicato.Effects.Bitcrusher({ - bits: Math.max(10, Scratch.Cast.toNumber(args.BITS)) / 10, - frequency : Math.max(30000, Scratch.Cast.toNumber(args.FREQ)) + bits: Math.max(10, Cast.toNumber(args.BITS)) / 10, + frequency : Math.max(30000, Cast.toNumber(args.FREQ)) }); this.updateEffect(bitcrush, sound, "BITCRUSH", args); } @@ -1043,9 +1073,9 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; const distort = new Pizzicato.Effects.Tremolo({ - speed: Scratch.Cast.toNumber(args.SPEED) / 5, - depth: Scratch.Cast.toNumber(args.DEPTH) / 100, - mix: Scratch.Cast.toNumber(args.MIX) / 100 + speed: Cast.toNumber(args.SPEED) / 5, + depth: Cast.toNumber(args.DEPTH) / 100, + mix: Cast.toNumber(args.MIX) / 100 }); this.updateEffect(distort, sound, "TREMOLO", args); } @@ -1053,7 +1083,7 @@ setPass(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - const json = { frequency: Scratch.Cast.toNumber(args.FREQ), peak:Scratch.Cast.toNumber(args.PEAK) / 5 }; + const json = { frequency: Cast.toNumber(args.FREQ), peak:Cast.toNumber(args.PEAK) / 5 }; if (args.TYPE === "highpass") { const highpass = new Pizzicato.Effects.HighPassFilter(json); this.updateEffect(highpass, sound, "HIGHPASS", args); @@ -1067,9 +1097,9 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; const flang = new Pizzicato.Effects.Flanger({ - time: Scratch.Cast.toNumber(args.TIME) / 100, speed: Scratch.Cast.toNumber(args.SPEED) / 100, - depth: Scratch.Cast.toNumber(args.DEPTH) / 100, feedback: Scratch.Cast.toNumber(args.FEED) / 100, - mix: Scratch.Cast.toNumber(args.MIX) / 100 + time: Cast.toNumber(args.TIME) / 100, speed: Cast.toNumber(args.SPEED) / 100, + depth: Cast.toNumber(args.DEPTH) / 100, feedback: Cast.toNumber(args.FEED) / 100, + mix: Cast.toNumber(args.MIX) / 100 }); this.updateEffect(flang, sound, "FLANGER", args); } @@ -1078,9 +1108,9 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; const compress = new Pizzicato.Effects.Compressor({ - threshold: Math.min(0, Math.max(-100, Scratch.Cast.toNumber(args.THRESH) * -1)), - ratio: Scratch.Cast.toNumber(args.RATIO) / 5, attack: Math.min(0, Math.max(1, Scratch.Cast.toNumber(args.ATTACK) / 100)), - release: Math.min(0, Math.max(1, Scratch.Cast.toNumber(args.RELEASE) / 100)), knee: Scratch.Cast.toNumber(args.KNEE) / 2.5 + threshold: Math.min(0, Math.max(-100, Cast.toNumber(args.THRESH) * -1)), + ratio: Cast.toNumber(args.RATIO) / 5, attack: Math.min(0, Math.max(1, Cast.toNumber(args.ATTACK) / 100)), + release: Math.min(0, Math.max(1, Cast.toNumber(args.RELEASE) / 100)), knee: Cast.toNumber(args.KNEE) / 2.5 }); this.updateEffect(compress, sound, "COMPRESSOR", args); } @@ -1089,11 +1119,11 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; const equalizer = new Pizzicato.Effects.ThreeBandEqualizer({ - cutoff_frequency_high: 12000 + (Scratch.Cast.toNumber(args.CUT_HIGH) * 120), - cutoff_frequency_low: 12000 + (Scratch.Cast.toNumber(args.CUT_LOW) * 120), - low_band_gain: Scratch.Cast.toNumber(args.LOW) / 10, - mid_band_gain: Scratch.Cast.toNumber(args.MID) / 10, - high_band_gain: Scratch.Cast.toNumber(args.HIGH) / 10 + cutoff_frequency_high: 12000 + (Cast.toNumber(args.CUT_HIGH) * 120), + cutoff_frequency_low: 12000 + (Cast.toNumber(args.CUT_LOW) * 120), + low_band_gain: Cast.toNumber(args.LOW) / 10, + mid_band_gain: Cast.toNumber(args.MID) / 10, + high_band_gain: Cast.toNumber(args.HIGH) / 10 }); this.updateEffect(equalizer, sound, "EQUALIZER", args); } From 70a1507cff7ada9e4a1c8b833c390f95add0aba2 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:18:15 -0800 Subject: [PATCH 24/40] Tune-Shark-V3 -- Cache, Improvements, Faster Vanilla Load Times --- extensions/SharkPool/Tune-Shark-V3.js | 172 ++++++++++++++++---------- 1 file changed, 110 insertions(+), 62 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 586687da02..59cb84ddf5 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.3.1 +// Version V.3.4.0 (function (Scratch) { "use strict"; @@ -27,7 +27,7 @@ // uses MIT License const scriptElement = document.createElement("script"); scriptElement.textContent = -`!function(e){"use strict";var t={},i=t,n="object"==typeof module&&module.exports,o="function"==typeof define&&define.amd;n?module.exports=t:o?define([],t):e.Pizzicato=e.Pz=t;var s=e.AudioContext||e.webkitAudioContext;if(!s){console.error("No AudioContext found in this environment. Please ensure your window or global object contains a working AudioContext constructor function.");return}t.context=new s;var a=t.context.createGain();a.connect(t.context.destination),t.Util={isString:function(e){return"[object String]"===toString.call(e)},isObject:function(e){return"[object Object]"===toString.call(e)},isFunction:function(e){return"[object Function]"===toString.call(e)},isNumber:function(e){return"[object Number]"===toString.call(e)&&e===+e},isArray:function(e){return"[object Array]"===toString.call(e)},isInRange:function(e,t,n){return!!(i.Util.isNumber(e)&&i.Util.isNumber(t)&&i.Util.isNumber(n))&&e>=t&&e<=n},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof i.Sound},isEffect:function(e){for(var i in t.Effects)if(e instanceof t.Effects[i])return!0;return!1},normalize:function(e,t,n){if(i.Util.isNumber(e)&&i.Util.isNumber(t)&&i.Util.isNumber(n))return(n-t)*e/1+t},getDryLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e<=.5?1:1-(e-.5)*2},getWetLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e>=.5?1:1-(.5-e)*2}};var r=Object.getPrototypeOf(Object.getPrototypeOf(t.context.createGain())),c=r.connect;r.connect=function(e){var t=i.Util.isEffect(e)?e.inputNode:e;return c.call(this,t),e},Object.defineProperty(t,"volume",{enumerable:!0,get:function(){return a.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&a&&(a.gain.value=e)}}),Object.defineProperty(t,"masterGainNode",{enumerable:!1,get:function(){return a},set:function(e){console.error("Can't set the master gain node")}}),t.Events={on:function(e,t,i){e&&t&&(this._events=this._events||{},(this._events[e]||(this._events[e]=[])).push({callback:t,context:i||this,handler:this}))},trigger:function(e){var t,i,n,o;if(e){for(this._events=this._events||{},t=this._events[e]||(this._events[e]=[]),o=0,i=Math.max(0,arguments.length-1),n=[];o1){e.shift(),h(e,i);return}t=t||Error("Error decoding audio file "+e[0]),s.isFunction(i)&&i(t)}).bind(o))},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e[0]+". "+n.statusText)},n.send()}function u(e,i){var n=s.isFunction(e)?e:e.audioFunction,o=s.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!o)try{t.context.createScriptProcessor()}catch(a){o=2048}this.getRawSourceNode=function(){var e=t.context.createScriptProcessor(o,1,1);return e.onaudioprocess=n,e}}this.detached=r&&e.options.detached,this.masterVolume=t.context.createGain(),this.fadeNode=t.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(t.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=r&&e.options.loop,this.attack=r&&s.isNumber(e.options.attack)?e.options.attack:.04,this.volume=r&&s.isNumber(e.options.volume)?e.options.volume:1,r&&s.isNumber(e.options.release)?this.release=e.options.release:r&&s.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=.04,e?s.isString(e)?h.bind(this)(e,n):s.isFunction(e)?u.bind(this)(e,n):"file"===e.source?h.bind(this)(e.options.path,n):"wave"===e.source?c.bind(this)(e.options,n):"input"===e.source?(function e(i,n){if(navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,!navigator.getUserMedia&&(!navigator.mediaDevices||navigator.mediaDevices.getUserMedia)){console.error("Your browser does not support getUserMedia. Note that the current document must be loaded securely for this to work");return}var a=(function(e){o.getRawSourceNode=function(){return t.context.createMediaStreamSource(e)},s.isFunction(n)&&n()}).bind(o),r=function(e){s.isFunction(n)&&n(e)};navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia({audio:!0}).then(a).catch(r):navigator.getUserMedia({audio:!0},a,r)}).bind(this)(e,n):"script"===e.source?u.bind(this)(e.options,n):"sound"===e.source&&(function e(t,n){this.getRawSourceNode=t.sound.getRawSourceNode,t.sound.sourceNode&&i.Util.isOscillator(t.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=t.sound.frequency)}).bind(this)(e.options,n):c.bind(this)({},n)},t.Sound.prototype=Object.create(t.Events,{play:{enumerable:!0,value:function(e,n){this.playing||(i.Util.isNumber(n)||(n=this.offsetTime||0),i.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),i.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=t.context.currentTime-n,this.sourceNode.start(i.context.currentTime+e,n)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=i.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/i.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new t.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),i=0;i0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this sound."),this;var n=this.playing;n&&this.pause();var o=0===i?this.fadeNode:this.effectConnectors[i-1];o.disconnect();var s=this.effectConnectors[i];return s.disconnect(),e.disconnect(s),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],o.connect(t),n&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.attack){this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,.001);return}var e=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,t=this.attack;e||(t=(1-this.fadeNode.gain.value)*this.attack),this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,2*t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,n=function(){return i.Util.isFunction(t.stop)?t.stop(0):t.disconnect()};if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.release){this.fadeNode.gain.setTargetAtTime(0,i.context.currentTime,.001),n();return}var o=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,s=this.release;o||(s=this.fadeNode.gain.value*this.release),this.fadeNode.gain.setTargetAtTime(1e-5,i.context.currentTime,s/5),window.setTimeout(function(){n()},1e3*s)}}}),t.Group=function(e){e=e||[],this.mergeGainNode=i.context.createGain(),this.masterVolume=i.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(i.masterGainNode);for(var t=0;t-1){console.warn("The Pizzicato.Sound object was already added to this group");return}if(e.detached){console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together.");return}e.disconnect(i.masterGainNode),e.connect(this.mergeGainNode),this.sounds.push(e)}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);if(-1===t){console.warn("Cannot remove a sound that is not part of this group.");return}e.disconnect(this.mergeGainNode),e.connect(i.masterGainNode),this.sounds.splice(t,1)}},volume:{enumerable:!0,get:function(){if(this.masterVolume)return this.masterVolume.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this group."),this;var n=0===i?this.mergeGainNode:this.effectConnectors[i-1];n.disconnect();var o=this.effectConnectors[i];return o.disconnect(),e.disconnect(o),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],n.connect(t),this}}}),t.Effects={};var h=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});function u(e,n){this.options={},e=e||this.options;var o={frequency:350,peak:1};for(var s in this.inputNode=this.filterNode=i.context.createBiquadFilter(),this.filterNode.type=n,this.outputNode=t.context.createGain(),this.filterNode.connect(this.outputNode),o)this[s]=e[s],this[s]=void 0===this[s]||null===this[s]?o[s]:this[s]}t.Effects.Delay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Delay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Compressor=function(e){this.options={},e=e||this.options;var i={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};for(var n in this.inputNode=this.compressorNode=t.context.createDynamicsCompressor(),this.outputNode=t.context.createGain(),this.compressorNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Compressor.prototype=Object.create(h,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){t.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){t.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){t.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),t.Effects.LowPassFilter=function(e){u.call(this,e,"lowpass")},t.Effects.HighPassFilter=function(e){u.call(this,e,"highpass")};var d=Object.create(h,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){t.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});function l(){var e,n,o=i.context.sampleRate*this.time,s=t.context.createBuffer(2,o,i.context.sampleRate),a=s.getChannelData(0),r=s.getChannelData(1);for(n=0;n-1?this.pannerNode.pan.value=e:this.pannerNode.setPosition(e,0,1-Math.abs(e))))}}}),t.Effects.Convolver=function(e,n){this.options={},e=e||this.options;var o=this,s=new XMLHttpRequest,a={mix:.5};for(var r in this.callback=n,this.inputNode=t.context.createGain(),this.convolverNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),a)this[r]=e[r],this[r]=void 0===this[r]||null===this[r]?a[r]:this[r];if(!e.impulse){console.error("No impulse file specified.");return}s.open("GET",e.impulse,!0),s.responseType="arraybuffer",s.onload=function(e){var n=e.target.response;t.context.decodeAudioData(n,function(e){o.convolverNode.buffer=e,o.callback&&i.Util.isFunction(o.callback)&&o.callback()},function(e){e=e||Error("Error decoding impulse file"),o.callback&&i.Util.isFunction(o.callback)&&o.callback(e)})},s.onreadystatechange=function(t){4===s.readyState&&200!==s.status&&console.error("Error while fetching "+e.impulse+". "+s.statusText)},s.send()},t.Effects.Convolver.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}}}),t.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.delayNodeLeft=t.context.createDelay(),this.delayNodeRight=t.context.createDelay(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.channelMerger=t.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.PingPongDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Reverb=function(e){this.options={},e=e||this.options;var i={mix:.5,time:.01,decay:.01,reverse:!1};for(var n in this.inputNode=t.context.createGain(),this.reverbNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n];l.bind(this)()},t.Effects.Reverb.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.time=e,l.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,l.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){i.Util.isBool(e)&&(this.options.reverse=e,l.bind(this)())}}}),t.Effects.Bitcrusher=function(e){this.inputNode=i.context.createGain(),this.outputNode=i.context.createGain(),this.bits=e.bits||4,this.frequency=e.frequency||44100,this.crusherNode=i.context.createScriptProcessor(4096,1,1);var t=this;this.crusherNode.onaudioprocess=function(e){for(var n=e.inputBuffer,o=e.outputBuffer,s=0;s=1&&e<=16&&(this.bits=e)},getBits:function(){return this.bits},setFrequency:function(e){e>0&&(this.frequency=e)},getFrequency:function(){return this.frequency}},t.Effects.ThreeBandEqualizer=function(e){f.call(this,e)};var p=Object.create(h,{cutoff_frequency_low:{enumerable:!0,get:function(){return this.options.cutoff_frequency_low},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_low=e,this.lowFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},cutoff_frequency_high:{enumerable:!0,get:function(){return this.options.cutoff_frequency_high},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_high=e,this.highFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},low_band_gain:{enumerable:!0,get:function(){return this.options.low_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.low_band_gain=e,this.lowGainNode.gain.value=Math.pow(10,e/20))}},mid_band_gain:{enumerable:!0,get:function(){return this.options.mid_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.mid_band_gain=e,this.midGainNode.gain.value=Math.pow(10,e/20))}},high_band_gain:{enumerable:!0,get:function(){return this.options.high_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.high_band_gain=e,this.highGainNode.gain.value=Math.pow(10,e/20))}},low_peak:{enumerable:!0,get:function(){return this.lowFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.lowFilterNode.Q.value=e)}},mid_peak:{enumerable:!0,get:function(){return this.midFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.midFilterNode.Q.value=e)}},high_peak:{enumerable:!0,get:function(){return this.highFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.highFilterNode.Q.value=e)}},visualizerBinCount:{enumerable:!0,get:function(){return this.analyserNode.frequencyBinCount},set:function(e){t.Util.isInRange(e,16,1024)&&(this.analyzerNode.fftSize=e)}},analyser:{enumerable:!0,get:function(){return this.analyserNode}},frequencyData:{enumerable:!0,get:function(){return void 0===this.byteFrequencyData&&(this.byteFrequencyData=new Uint8Array(this.analyserNode.frequencyBinCount.value)),this.analyserNode.getByteFrequencyData(this.FrequencyData),this.byteFrequencyData}}});t.Effects.ThreeBandEqualizer.prototype=p,t.Effects.Tremolo=function(e){this.options={},e=e||this.options;var i={speed:4,depth:1,mix:.8};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.tremoloGainNode=t.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=t.context.createOscillator(),this.shaperNode=t.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Tremolo.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){i.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),t.Effects.DubDelay=function(e){this.options={},e=e||this.options;var i={feedback:.6,time:.7,mix:.5,cutoff:700};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.bqFilterNode=t.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.DubDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){i.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),t.Effects.RingModulator=function(e){this.options={},e=e||this.options;var i={speed:30,distortion:1,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.vIn=t.context.createOscillator(),this.vIn.start(0),this.vInGain=t.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=t.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=t.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new N(t.context),this.vInDiode2=new N(t.context),this.vInInverter3=t.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=t.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new N(t.context),this.vcDiode4=new N(t.context),this.outGain=t.context.createGain(),this.outGain.gain.value=3,this.compressor=t.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]};var N=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};function v(e){for(var t=i.context.sampleRate,n=new Float32Array(t),o=Math.PI/180,s=0;sa;e=0<=a?++s:--s)n=(i=Math.abs(i=(e-t/2)/(t/2)))<=this.vb?0:this.vb=t&&e<=n},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof i.Sound},isEffect:function(e){for(var i in t.Effects)if(e instanceof t.Effects[i])return!0;return!1},normalize:function(e,t,n){if(i.Util.isNumber(e)&&i.Util.isNumber(t)&&i.Util.isNumber(n))return(n-t)*e/1+t},getDryLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e<=.5?1:1-(e-.5)*2},getWetLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e>=.5?1:1-(.5-e)*2}};var a=Object.getPrototypeOf(Object.getPrototypeOf(t.context.createGain())),c=a.connect;a.connect=function(e){var t=i.Util.isEffect(e)?e.inputNode:e;return c.call(this,t),e},Object.defineProperty(t,"volume",{enumerable:!0,get:function(){return r.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&r&&(r.gain.value=e)}}),Object.defineProperty(t,"masterGainNode",{enumerable:!1,get:function(){return r},set:function(e){console.error("Can't set the master gain node")}}),t.Events={on:function(e,t,i){e&&t&&(this._events=this._events||{},(this._events[e]||(this._events[e]=[])).push({callback:t,context:i||this,handler:this}))},trigger:function(e){var t,i,n,o;if(e){for(this._events=this._events||{},t=this._events[e]||(this._events[e]=[]),o=0,i=Math.max(0,arguments.length-1),n=[];o1){e.shift(),h(e,i);return}t=t||Error("Error decoding audio file "+e[0]),s.isFunction(i)&&i(t)}).bind(o))},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e[0]+". "+n.statusText)},n.send()}function u(e,i){var n=s.isFunction(e)?e:e.audioFunction,o=s.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!o)try{t.context.createScriptProcessor()}catch(r){o=2048}this.getRawSourceNode=function(){var e=t.context.createScriptProcessor(o,1,1);return e.onaudioprocess=n,e}}this.detached=a&&e.options.detached,this.masterVolume=t.context.createGain(),this.fadeNode=t.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(t.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=a&&e.options.loop,this.attack=a&&s.isNumber(e.options.attack)?e.options.attack:.04,this.volume=a&&s.isNumber(e.options.volume)?e.options.volume:1,a&&s.isNumber(e.options.release)?this.release=e.options.release:a&&s.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=.04,e?s.isString(e)?h.bind(this)(e,n):s.isFunction(e)?u.bind(this)(e,n):"file"===e.source?h.bind(this)(e.options.path,n):"buffer"===e.source?(function e(i,n){if((i=Array.isArray(i)?i:[i])[0]instanceof AudioBuffer){let s=i[0];o.getRawSourceNode=function(){let e=t.context.createBufferSource();return e.loop=this.loop,e.buffer=s,e},"function"==typeof n&&n()}}).bind(this)(e.options.buffer,n):"wave"===e.source?c.bind(this)(e.options,n):"input"===e.source?(function e(i,n){if(navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,!navigator.getUserMedia&&(!navigator.mediaDevices||navigator.mediaDevices.getUserMedia)){console.error("Your browser does not support getUserMedia. Note that the current document must be loaded securely for this to work");return}var r=(function(e){o.getRawSourceNode=function(){return t.context.createMediaStreamSource(e)},s.isFunction(n)&&n()}).bind(o),a=function(e){s.isFunction(n)&&n(e)};navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia({audio:!0}).then(r).catch(a):navigator.getUserMedia({audio:!0},r,a)}).bind(this)(e,n):"script"===e.source?u.bind(this)(e.options,n):"sound"===e.source&&(function e(t,n){this.getRawSourceNode=t.sound.getRawSourceNode,t.sound.sourceNode&&i.Util.isOscillator(t.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=t.sound.frequency)}).bind(this)(e.options,n):c.bind(this)({},n)},t.Sound.prototype=Object.create(t.Events,{play:{enumerable:!0,value:function(e,n){this.playing||(i.Util.isNumber(n)||(n=this.offsetTime||0),i.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),i.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=t.context.currentTime-n,this.sourceNode.start(i.context.currentTime+e,n)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=i.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/i.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new t.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),i=0;i0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this sound."),this;var n=this.playing;n&&this.pause();var o=0===i?this.fadeNode:this.effectConnectors[i-1];o.disconnect();var s=this.effectConnectors[i];return s.disconnect(),e.disconnect(s),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],o.connect(t),n&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.attack){this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,.001);return}var e=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,t=this.attack;e||(t=(1-this.fadeNode.gain.value)*this.attack),this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,2*t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,n=function(){return i.Util.isFunction(t.stop)?t.stop(0):t.disconnect()};if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.release){this.fadeNode.gain.setTargetAtTime(0,i.context.currentTime,.001),n();return}var o=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,s=this.release;o||(s=this.fadeNode.gain.value*this.release),this.fadeNode.gain.setTargetAtTime(1e-5,i.context.currentTime,s/5),window.setTimeout(function(){n()},1e3*s)}}}),t.Group=function(e){e=e||[],this.mergeGainNode=i.context.createGain(),this.masterVolume=i.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(i.masterGainNode);for(var t=0;t-1){console.warn("The Pizzicato.Sound object was already added to this group");return}if(e.detached){console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together.");return}e.disconnect(i.masterGainNode),e.connect(this.mergeGainNode),this.sounds.push(e)}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);if(-1===t){console.warn("Cannot remove a sound that is not part of this group.");return}e.disconnect(this.mergeGainNode),e.connect(i.masterGainNode),this.sounds.splice(t,1)}},volume:{enumerable:!0,get:function(){if(this.masterVolume)return this.masterVolume.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this group."),this;var n=0===i?this.mergeGainNode:this.effectConnectors[i-1];n.disconnect();var o=this.effectConnectors[i];return o.disconnect(),e.disconnect(o),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],n.connect(t),this}}}),t.Effects={};var h=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});function u(e,n){this.options={},e=e||this.options;var o={frequency:350,peak:1};for(var s in this.inputNode=this.filterNode=i.context.createBiquadFilter(),this.filterNode.type=n,this.outputNode=t.context.createGain(),this.filterNode.connect(this.outputNode),o)this[s]=e[s],this[s]=void 0===this[s]||null===this[s]?o[s]:this[s]}t.Effects.Delay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Delay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Compressor=function(e){this.options={},e=e||this.options;var i={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};for(var n in this.inputNode=this.compressorNode=t.context.createDynamicsCompressor(),this.outputNode=t.context.createGain(),this.compressorNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Compressor.prototype=Object.create(h,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){t.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){t.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){t.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),t.Effects.LowPassFilter=function(e){u.call(this,e,"lowpass")},t.Effects.HighPassFilter=function(e){u.call(this,e,"highpass")};var d=Object.create(h,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){t.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});function l(){var e,n,o=i.context.sampleRate*this.time,s=t.context.createBuffer(2,o,i.context.sampleRate),r=s.getChannelData(0),a=s.getChannelData(1);for(n=0;n-1?this.pannerNode.pan.value=e:this.pannerNode.setPosition(e,0,1-Math.abs(e))))}}}),t.Effects.Convolver=function(e,n){this.options={},e=e||this.options;var o=this,s=new XMLHttpRequest,r={mix:.5};for(var a in this.callback=n,this.inputNode=t.context.createGain(),this.convolverNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),r)this[a]=e[a],this[a]=void 0===this[a]||null===this[a]?r[a]:this[a];if(!e.impulse){console.error("No impulse file specified.");return}s.open("GET",e.impulse,!0),s.responseType="arraybuffer",s.onload=function(e){var n=e.target.response;t.context.decodeAudioData(n,function(e){o.convolverNode.buffer=e,o.callback&&i.Util.isFunction(o.callback)&&o.callback()},function(e){e=e||Error("Error decoding impulse file"),o.callback&&i.Util.isFunction(o.callback)&&o.callback(e)})},s.onreadystatechange=function(t){4===s.readyState&&200!==s.status&&console.error("Error while fetching "+e.impulse+". "+s.statusText)},s.send()},t.Effects.Convolver.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}}}),t.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.delayNodeLeft=t.context.createDelay(),this.delayNodeRight=t.context.createDelay(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.channelMerger=t.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.PingPongDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Reverb=function(e){this.options={},e=e||this.options;var i={mix:.5,time:.01,decay:.01,reverse:!1};for(var n in this.inputNode=t.context.createGain(),this.reverbNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n];l.bind(this)()},t.Effects.Reverb.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.time=e,l.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,l.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){i.Util.isBool(e)&&(this.options.reverse=e,l.bind(this)())}}}),t.Effects.Bitcrusher=function(e){this.inputNode=i.context.createGain(),this.outputNode=i.context.createGain(),this.bits=e.bits||4,this.frequency=e.frequency||44100,this.crusherNode=i.context.createScriptProcessor(4096,1,1);var t=this;this.crusherNode.onaudioprocess=function(e){for(var n=e.inputBuffer,o=e.outputBuffer,s=0;s=1&&e<=16&&(this.bits=e)},getBits:function(){return this.bits},setFrequency:function(e){e>0&&(this.frequency=e)},getFrequency:function(){return this.frequency}},t.Effects.ThreeBandEqualizer=function(e){f.call(this,e)};var p=Object.create(h,{cutoff_frequency_low:{enumerable:!0,get:function(){return this.options.cutoff_frequency_low},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_low=e,this.lowFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},cutoff_frequency_high:{enumerable:!0,get:function(){return this.options.cutoff_frequency_high},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_high=e,this.highFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},low_band_gain:{enumerable:!0,get:function(){return this.options.low_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.low_band_gain=e,this.lowGainNode.gain.value=Math.pow(10,e/20))}},mid_band_gain:{enumerable:!0,get:function(){return this.options.mid_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.mid_band_gain=e,this.midGainNode.gain.value=Math.pow(10,e/20))}},high_band_gain:{enumerable:!0,get:function(){return this.options.high_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.high_band_gain=e,this.highGainNode.gain.value=Math.pow(10,e/20))}},low_peak:{enumerable:!0,get:function(){return this.lowFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.lowFilterNode.Q.value=e)}},mid_peak:{enumerable:!0,get:function(){return this.midFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.midFilterNode.Q.value=e)}},high_peak:{enumerable:!0,get:function(){return this.highFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.highFilterNode.Q.value=e)}},visualizerBinCount:{enumerable:!0,get:function(){return this.analyserNode.frequencyBinCount},set:function(e){t.Util.isInRange(e,16,1024)&&(this.analyzerNode.fftSize=e)}},analyser:{enumerable:!0,get:function(){return this.analyserNode}},frequencyData:{enumerable:!0,get:function(){return void 0===this.byteFrequencyData&&(this.byteFrequencyData=new Uint8Array(this.analyserNode.frequencyBinCount.value)),this.analyserNode.getByteFrequencyData(this.FrequencyData),this.byteFrequencyData}}});t.Effects.ThreeBandEqualizer.prototype=p,t.Effects.Tremolo=function(e){this.options={},e=e||this.options;var i={speed:4,depth:1,mix:.8};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.tremoloGainNode=t.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=t.context.createOscillator(),this.shaperNode=t.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Tremolo.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){i.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),t.Effects.DubDelay=function(e){this.options={},e=e||this.options;var i={feedback:.6,time:.7,mix:.5,cutoff:700};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.bqFilterNode=t.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.DubDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){i.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),t.Effects.RingModulator=function(e){this.options={},e=e||this.options;var i={speed:30,distortion:1,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.vIn=t.context.createOscillator(),this.vIn.start(0),this.vInGain=t.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=t.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=t.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new N(t.context),this.vInDiode2=new N(t.context),this.vInInverter3=t.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=t.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new N(t.context),this.vcDiode4=new N(t.context),this.outGain=t.context.createGain(),this.outGain.gain.value=3,this.compressor=t.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]};var N=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};function v(e){for(var t=i.context.sampleRate,n=new Float32Array(t),o=Math.PI/180,s=0;sr;e=0<=r?++s:--s)n=(i=Math.abs(i=(e-t/2)/(t/2)))<=this.vb?0:this.vb { if (storage === undefined) return; settings = storage.settings; soundBank = storage.bank; for (const item in soundBank) { - soundBank[item].loaded = false; - const engine = new Pizzicato.Sound(soundBank[item].src, () => { + const sound = soundBank[item]; + sound.loaded = false; + if (sound.isVanilla) { + const info = sound.src.substring(1, sound.src.lastIndexOf(".")).split("/"); + let target = info[0] === "Stage" ? runtime.getTargetForStage() : runtime.getSpriteTargetByName(info[0]); + if (target === undefined) { + alert(`Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}`); + continue; + } + + const scratchSound = target.sprite.sounds.find((i) => { return i.name === info[1] }); + if (scratchSound === undefined) { + alert(`Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}`); + continue; + } + const buffer = target.sprite.soundBank.soundPlayers[scratchSound.soundId].buffer; + const engine = new Pizzicato.Sound({ + source: "buffer", options: { buffer, attack: 0 } + }); + engine.sourceNode = engine.getSourceNode(); - soundBank[item].context = engine; - soundBank[item].loaded = true; - }); + sound.context = engine; + sound.loaded = true; + } else { + const engine = new Pizzicato.Sound({ + source: "file", options: { path: sound.src, attack: 0 } + }, () => { + engine.sourceNode = engine.getSourceNode(); + sound.context = engine; + sound.loaded = true; + }); + } } }; - if (!Scratch.extensions.isPenguinMod) load(runtime.extensionStorage["SPtuneShark3"]); + if (!Scratch.extensions.isPenguinMod) { + runtime.on("PROJECT_LOADED", () => { load(runtime.extensionStorage["SPtuneShark3"]) }) + } // Create an Event for when Pause Project is Activated // Save original function if it exists @@ -620,7 +648,7 @@ this.patchLinks(context.sourceNode, thisSound); }); } - if (sound.loop) this.loopParams({ NAME : con.name , START : con.loopParm[0], END : con.loopParm[1] }); + if (sound.loop) this.loopParams({ NAME: con.name, START: con.loopParm[0], END: con.loopParm[1] }); } this.startHats({ name: con.name, type: "starts" }); } catch { @@ -684,7 +712,7 @@ src.playbackRate.value = sound.pitch; src.detune.value = sound.detune; src.gainSuccessor.gain.value = sound.gain; - if (src.loop) this.loopParams({ NAME : sound.name , START : sound.loopParm[0], END : sound.loopParm[1] }); + if (src.loop) this.loopParams({ NAME: sound.name, START: sound.loopParm[0], END: sound.loopParm[1] }); } startHats(data) { @@ -702,19 +730,19 @@ return new Promise((resolve) => { this.deleteSound(args); if (!args.URL) return resolve(); - const engine = new Pizzicato.Sound(args.URL, () => { + const engine = new Pizzicato.Sound({ + source: "file", options: { path: args.URL, attack: 0 } + }, () => { try { engine.sourceNode = engine.getSourceNode(); - engine.attack = 0; soundBank[args.NAME] = { context: engine, name: args.NAME, src: args.URL, effects: {}, pauseTime: 0, rateChange: [0,0], loaded: true, reversed: false, vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, loopParm: [0, 0], overlap: false, overlays: [], - isBind: false, binds: {} + isBind: false, binds: {}, _cache: { loudness: {}, tone: {} }, isVanilla: false }; resolve(); } catch { - // File is Corrupted/Doesnt Exist/is a unedited Scratch Sound alert("Tune Shark V3 Cant Import this Sound, File may be Corrupted or Non-Existent"); resolve(); } @@ -723,14 +751,25 @@ } async importMenu(args, util) { - if (runtime.isPackaged) { - alert(`For "Import Scratch Sound" (Tune Shark) to Work, Disable "Remove raw asset data after loading to save RAM" under advanced settings in the packager.`); - return; - } const name = Cast.toString(args.SOUND); const target = util.target.sprite; - const sound = target.sounds.filter((i) => { return i.name === name })[0]; - if (sound) await this.importURL({ ...args, URL: sound.asset.encodeDataURI() }, util); + const sound = target.sounds.find((i) => { return i.name === name }); + if (sound) { + this.deleteSound(args); + const sourceURL = `/${target.name.replaceAll("/", "")}/${sound.name.replaceAll("/", "")}.${sound.dataFormat}`; + const buffer = target.soundBank.soundPlayers[sound.soundId].buffer; + const engine = new Pizzicato.Sound({ + source: "buffer", options: { buffer, attack: 0 } + }); + // this part of the Library was modified to work like this + engine.sourceNode = engine.getSourceNode(); + soundBank[args.NAME] = { + context: engine, name: args.NAME, src: sourceURL, + effects: {}, pauseTime: 0, rateChange: [0,0], loaded: true, reversed: false, vol: 100, + gain: 1, pitch: 1, detune: 0, speed: 1, loopParm: [0, 0], overlap: false, overlays: [], + isBind: false, binds: {}, _cache: { loudness: {}, tone: {} }, isVanilla: true + }; + } } async convertSound(args, util) { @@ -828,6 +867,7 @@ if (sound.reversed === (args.TYPE === "on")) return; sound.reversed = args.TYPE === "on"; this.typeOverlay(sound, "stop"); + sound._cache = { loudness: {}, tone: {} }; const node = sound.context.sourceNode; const reverseBuffer = (buffer) => { for (let i = 0; i < buffer.numberOfChannels; i++) buffer.getChannelData(i).reverse(); @@ -925,47 +965,55 @@ const time = Cast.toNumber(args.TIME); const duration = sound.context.sourceNode.buffer.duration; if (time < 0 || time > duration) return 0; - const audioBuffer = sound.context.sourceNode.buffer; - const sampleRate = audioBuffer.sampleRate; - const channelData = audioBuffer.getChannelData(0); - const sampleIndex = Math.floor(sampleRate * time); - const windowSize = sampleRate * 0.1; - const startSample = Math.max(0, sampleIndex - windowSize / 2); - const endSample = Math.min(channelData.length, sampleIndex + windowSize / 2); - let sample = 0; - if (args.TYPE === "raw noise") sample = channelData[endSample]; - else if (args.TYPE === "tone") { - const data = channelData.slice(startSample, endSample); - const size = data.length; - const tauArray = new Array(size).fill(0); - for (let tau = 1; tau < size; tau++) { - let sum = 0; - for (let i = 0; i < size - tau; i++) { - const diff = data[i] - data[i + tau]; - sum += diff * diff; + + let value = 0; + if (sound._cache[args.TYPE][time] !== undefined) value = sound._cache[args.TYPE][time]; + else { + const audioBuffer = sound.context.sourceNode.buffer; + const sampleRate = audioBuffer.sampleRate; + const channelData = audioBuffer.getChannelData(0); + const sampleIndex = Math.floor(sampleRate * time); + const windowSize = sampleRate * 0.1; + const startSample = Math.max(0, sampleIndex - windowSize / 2); + const endSample = Math.min(channelData.length, sampleIndex + windowSize / 2); + + // no need to cache raw noise, no work is done + if (args.TYPE === "raw noise") value = channelData[endSample]; + else if (args.TYPE === "tone") { + const data = channelData.slice(startSample, endSample); + const size = data.length; + const tauArray = new Array(size).fill(0); + for (let tau = 1; tau < size; tau++) { + let sum = 0; + for (let i = 0; i < size - tau; i++) { + const diff = data[i] - data[i + tau]; + sum += diff * diff; + } + tauArray[tau] = sum; } - tauArray[tau] = sum; - } - for (let tau = 1; tau < size; tau++) { - sample += tauArray[tau]; - tauArray[tau] *= tau / sample; - } - let bestTau = -1; - for (let tau = 1; tau < size; tau++) { - if (tauArray[tau] < 0.1) { - bestTau = tau; - break; + for (let tau = 1; tau < size; tau++) { + value += tauArray[tau]; + tauArray[tau] *= tau / value; } - } - if (bestTau > 0) return sampleRate / bestTau; - return 0; - } else { - for (let i = startSample; i < endSample; i++) sample += channelData[i] * channelData[i]; - const rms = Math.sqrt(sample / (endSample - startSample)); - const dB = 20 * Math.log10(rms); - sample = Math.min(Math.max((dB + 50) / 50, 0), 1) * 100; + let bestTau = -1; + for (let tau = 1; tau < size; tau++) { + if (tauArray[tau] < 0.1) { + bestTau = tau; + break; + } + } + value = bestTau > 0 ? sampleRate / bestTau : 0; + sound._cache["tone"][time] = value; + return value; + } else if (args.TYPE === "loudness") { + for (let i = startSample; i < endSample; i++) value += channelData[i] * channelData[i]; + const rms = Math.sqrt(value / (endSample - startSample)); + const dB = 20 * Math.log10(rms); + value = Math.min(Math.max((dB + 50) / 50, 0), 1) * 100; + sound._cache["loudness"][time] = value; + } else { return "" } } - return isNaN(sample) ? 0 : sample * sound.gain; + return isNaN(value) ? 0 : value * sound.gain; } setVol(args) { @@ -1064,7 +1112,7 @@ */ const bitcrush = new Pizzicato.Effects.Bitcrusher({ bits: Math.max(10, Cast.toNumber(args.BITS)) / 10, - frequency : Math.max(30000, Cast.toNumber(args.FREQ)) + frequency: Math.max(30000, Cast.toNumber(args.FREQ)) }); this.updateEffect(bitcrush, sound, "BITCRUSH", args); } @@ -1134,7 +1182,7 @@ if (settings.canSave) { const convertBank = JSON.parse(JSON.stringify(soundBank)); Object.values(convertBank).forEach(item => delete item.context); - runtime.extensionStorage["SPtuneShark3"] = { bank : convertBank, settings }; + runtime.extensionStorage["SPtuneShark3"] = { bank: convertBank, settings }; } else { runtime.extensionStorage["SPtuneShark3"] = undefined } } } @@ -1144,7 +1192,7 @@ if (settings.canSave) { const convertBank = JSON.parse(JSON.stringify(soundBank)); Object.values(convertBank).forEach(item => delete item.context); - return { SPtuneShark3 : { bank : convertBank, settings } } + return { SPtuneShark3: { bank: convertBank, settings } } } } deserialize(data) { load(data.SPtuneShark3) } From 7c2bf774e2d41a87c8b750bc4285a15fcae0bf70 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:20:52 -0800 Subject: [PATCH 25/40] no need for that async --- extensions/SharkPool/Tune-Shark-V3.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 59cb84ddf5..f655c00709 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -750,7 +750,7 @@ }); } - async importMenu(args, util) { + importMenu(args, util) { const name = Cast.toString(args.SOUND); const target = util.target.sprite; const sound = target.sounds.find((i) => { return i.name === name }); From 034313b827f3e8fa5cb425188267300d52ac2544 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Fri, 3 Jan 2025 16:52:54 -0800 Subject: [PATCH 26/40] Tune-Shark-V3 -- Channels --- extensions/SharkPool/Tune-Shark-V3.js | 29 ++++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index f655c00709..aee1d89233 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.4.0 +// Version V.3.4.1 (function (Scratch) { "use strict"; @@ -363,12 +363,13 @@ { opcode: "getLoudTime", blockType: Scratch.BlockType.REPORTER, - text: "[TYPE] of sound [NAME] at time [TIME]", + text: "[TYPE] of sound [NAME] at time [TIME] in channel [CHANNEL]", blockIconURI: extraIcons.set, arguments: { TYPE: { type: Scratch.ArgumentType.STRING, menu: "loudProps" }, NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } + TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + CHANNEL: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 } }, }, { blockType: Scratch.BlockType.LABEL, text: "Audio Effects" }, @@ -534,7 +535,7 @@ soundProps: { acceptReporters: true, items: [ - "length", "current time", "estimated bpm", "source", "binds", "volume", + "length", "current time", "source", "estimated bpm", "channels", "binds", "volume", ].concat(simpleEffects, complexEffects) }, soundBools: { @@ -940,6 +941,7 @@ case "length": return src.buffer.duration; case "current time": return this.currentTime(sound, sound.context, src); case "estimated bpm": return this.getBPM(src.buffer.getChannelData(0), src.buffer.sampleRate); + case "channels": return src.buffer.numberOfChannels; case "source": return sound.src; case "binds": return JSON.stringify(Object.keys(sound.binds)); case "volume": return sound.vol; @@ -963,15 +965,18 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return 0; const time = Cast.toNumber(args.TIME); - const duration = sound.context.sourceNode.buffer.duration; - if (time < 0 || time > duration) return 0; + const chan = Cast.toNumber(args.CHANNEL) - 1; + const audioCtx = sound.context.sourceNode; + const buffer = audioCtx.buffer; + const duration = buffer.duration; + if (time < 0 || time > duration || chan < 0 || chan > buffer.numberOfChannels - 1) return 0; let value = 0; - if (sound._cache[args.TYPE][time] !== undefined) value = sound._cache[args.TYPE][time]; + if (args.TYPE !== "raw noise" && sound._cache[args.TYPE][`${time}${chan}`] !== undefined) + value = sound._cache[args.TYPE][`${time}${chan}`]; else { - const audioBuffer = sound.context.sourceNode.buffer; - const sampleRate = audioBuffer.sampleRate; - const channelData = audioBuffer.getChannelData(0); + const sampleRate = buffer.sampleRate; + const channelData = buffer.getChannelData(chan); const sampleIndex = Math.floor(sampleRate * time); const windowSize = sampleRate * 0.1; const startSample = Math.max(0, sampleIndex - windowSize / 2); @@ -1003,14 +1008,14 @@ } } value = bestTau > 0 ? sampleRate / bestTau : 0; - sound._cache["tone"][time] = value; + sound._cache["tone"][`${time}${chan}`] = value; return value; } else if (args.TYPE === "loudness") { for (let i = startSample; i < endSample; i++) value += channelData[i] * channelData[i]; const rms = Math.sqrt(value / (endSample - startSample)); const dB = 20 * Math.log10(rms); value = Math.min(Math.max((dB + 50) / 50, 0), 1) * 100; - sound._cache["loudness"][time] = value; + sound._cache["loudness"][`${time}${chan}`] = value; } else { return "" } } return isNaN(value) ? 0 : value * sound.gain; From e9e889b20d271cc28152609f66f84c126bb47b8a Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:41:20 -0800 Subject: [PATCH 27/40] Tune-Shark-V3 -- Improvements, Bug Fixes, and Current Time Fixes --- extensions/SharkPool/Tune-Shark-V3.js | 308 +++++++++++++------------- 1 file changed, 155 insertions(+), 153 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index aee1d89233..9572927d36 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.4.1 +// Version V.3.4.2 (function (Scratch) { "use strict"; @@ -16,12 +16,12 @@ "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIj48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Im02MS4yMDIgNTMuODM2LS4wMDItLjAwM2MtLjA2OCAzLjcxLTMuMzM3IDcuMjgtOC4wMTYgOC40MTYtNS4zNTkgMS4zMDItMTAuNTM4LTEuMDgtMTEuNTY4LTUuMzJzMi40OC04LjczMyA3LjgzOS0xMC4wMzVjMi40NC0uNTkzIDQuODQtLjQyIDYuODMxLjM0MnYtMjAuNTZjLTE0Ljg1IDEuMDAzLTI2LjI5OCA1LjAwNi0yNi4yOTggNS4wMDZ2MjcuNjQ0Yy40OSA0LjAxNC0yLjkxNiA4LjA3OC03Ljk3IDkuMzA2LTUuMzU4IDEuMzAxLTEwLjUzNy0xLjA4MS0xMS41NjctNS4zMjFzMi40OC04LjczMyA3LjgzOC0xMC4wMzVjMi40ODYtLjYwNCA0LjkzMy0uNDE0IDYuOTQ2LjM4NGwuMDEyLTM2Ljk5OHMxNi4xOS03LjI5IDM1Ljk1NS02LjYzM3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; const extraIcons = { - set: "B4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiID48cGF0aCBkPSJtNjQuNTMzIDQyLjYxIDIuMDQyLjg1NWE1LjAyIDUuMDIgMCAwIDEgMi42OSA2LjU3bC0xLjM3IDMuMjc0YTUuMDIgNS4wMiAwIDAgMS02LjU3IDIuNjlsLTIuMDQyLS44NTVhMjUgMjUgMCAwIDEtNC4yOTUgNC4yNmwuODQgMi4wNWE1LjAyIDUuMDIgMCAwIDEtMi43NDIgNi41NDhsLTMuMjg1IDEuMzQ1YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtLjg0LTIuMDVhMjUgMjUgMCAwIDEtNi4wNDktLjAyMmwtLjg1NSAyLjA0MmE1LjAyIDUuMDIgMCAwIDEtNi41NyAyLjY5bC0zLjI3NC0xLjM3YTUuMDIgNS4wMiAwIDAgMS0yLjY5LTYuNTdsLjg1NS0yLjA0MmEyNSAyNSAwIDAgMS00LjI2LTQuMjk1bC0yLjA1Ljg0YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtMS4zNDUtMy4yODVhNS4wMiA1LjAyIDAgMCAxIDIuNzQyLTYuNTQ4bDIuMDUtLjg0YTI1IDI1IDAgMCAxIC4wMjItNi4wNDlsLTIuMDQyLS44NTVhNS4wMiA1LjAyIDAgMCAxLTIuNjktNi41N2wxLjM3LTMuMjc0YTUuMDIgNS4wMiAwIDAgMSA2LjU3LTIuNjlsMi4wNDIuODU1YTI1IDI1IDAgMCAxIDQuMjk1LTQuMjZsLS44NC0yLjA1YTUuMDIgNS4wMiAwIDAgMSAyLjc0Mi02LjU0OGwzLjI4NS0xLjM0NWE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsLjg0IDIuMDVhMjUgMjUgMCAwIDEgNi4wNDkuMDIybC44NTUtMi4wNDJhNS4wMiA1LjAyIDAgMCAxIDYuNTctMi42OWwzLjI3NCAxLjM3YTUuMDIgNS4wMiAwIDAgMSAyLjY5IDYuNTdsLS44NTUgMi4wNDJhMjUgMjUgMCAwIDEgNC4yNiA0LjI5NWwyLjA1LS44NGE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsMS4zNDUgMy4yODVhNS4wMiA1LjAyIDAgMCAxLTIuNzQyIDYuNTQ4bC0yLjA1Ljg0YTI1IDI1IDAgMCAxLS4wMjIgNi4wNDltLTM3LjQ5OC04LjMzOGMtMi44OCA2Ljg3Ny4zNiAxNC43ODcgNy4yMzcgMTcuNjY3czE0Ljc4Ny0uMzYgMTcuNjY3LTcuMjM3LS4zNi0xNC43ODctNy4yMzctMTcuNjY3LTE0Ljc4Ny4zNi0xNy42NjcgNy4yMzciIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjwvZz48L3N2Zz4=", - nob: "B4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0wIDc4Ljk3NFYwaDc4Ljk3NHY3OC45NzR6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTM3Ljk2MSAxMC44NDdhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDRWNS42OTJjMC0xLjE2My45NDItMi4xMDUgMi4xMDQtMi4xMDVoMy4wNTJjMS4xNjIgMCAyLjEwNC45NDIgMi4xMDQgMi4xMDV2My4wNTFhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQgMi4xMDV6bTAgNjQuNTRhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDV2LTMuMDUxYzAtMS4xNjIuOTQyLTIuMTA1IDIuMTA0LTIuMTA1aDMuMDUyYzEuMTYyIDAgMi4xMDQuOTQzIDIuMTA0IDIuMTA1djMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA0IDIuMTA1em0yMC42OTgtNTcuMjNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6TTEzLjAyMyA2My43OTNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6bTU1LjEwNC0yNS44MzJjMC0xLjE2Mi45NDItMi4xMDQgMi4xMDQtMi4xMDRoMy4wNTFjMS4xNjMgMCAyLjEwNS45NDIgMi4xMDUgMi4xMDR2My4wNTJhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDUgMi4xMDRoLTMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA1LTIuMTA0em0tNjQuNTQgMGMwLTEuMTYyLjk0Mi0yLjEwNCAyLjEwNS0yLjEwNGgzLjA1MWMxLjE2MiAwIDIuMTA1Ljk0MiAyLjEwNSAyLjEwNHYzLjA1MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNSAyLjEwNEg1LjY5MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNS0yLjEwNHptNTcuMjMgMjAuNjk4YTIuMTA1IDIuMTA1IDAgMCAxIDIuOTc2IDBsMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMCAyLjk3NmwtMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEtMi45NzYgMGwtMi4xNTgtMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NnpNMTUuMTgxIDEzLjAyM2EyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDBsLTIuMTU4LTIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAtMi45NzZ6bTguNDE2IDEwLjQzYzcuNTQ2LTcuNTQ3IDE5LjA2NS04LjcwMiAyNy44MjctMy40NjUtLjEyNS4wOS0xMy4yNjQgMTcuODcyLTEyLjEzMyAxOS4wMDNsMS4wMzcgMS4wMzdjMS4xMyAxLjEzIDE4LjkxMy0xMi4wMDggMTkuMDAzLTEyLjEzMyA1LjIzNyA4Ljc2MiA0LjA4MiAyMC4yOC0zLjQ2NSAyNy44MjgtOC45MTEgOC45MS0yMy4zNTkgOC45MS0zMi4yNyAwLTguOTEtOC45MTEtOC45MS0yMy4zNTkgMC0zMi4yNyIgZmlsbD0iI2ZmZiIvPjwvZz48L3N2Zz4=", - flag: "B4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48cGF0aCBkPSJNLjc1IDJhNi40NCA2LjQ0IDAgMCAxIDcuNjkgMGgwYTYuNDQgNi40NCAwIDAgMCA3LjY5IDB2MTAuNGE2LjQ0IDYuNDQgMCAwIDEtNy42OSAwaDBhNi40NCA2LjQ0IDAgMCAwLTcuNjkgMCIgc3R5bGU9ImZpbGw6IzRjYmY1NjtzdHJva2U6IzQ1OTkzZDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQiLz48cGF0aCBzdHlsZT0iZmlsbDojNGNiZjU2O3N0cm9rZTojNDU5OTNkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MS41cHgiIGQ9Ik0uNzUgMTYuNzV2LTE2Ii8+PC9zdmc+", - stop: "B4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNCAxNCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6I2VjNTk1OTtzdHJva2U6I2I4NDg0ODtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIGQ9Ik00LjMuNWg1LjRsMy44IDMuOHY1LjRsLTMuOCAzLjhINC4zTC41IDkuN1Y0LjN6Ii8+PC9zdmc+" + set: "dpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiID48cGF0aCBkPSJtNjQuNTMzIDQyLjYxIDIuMDQyLjg1NWE1LjAyIDUuMDIgMCAwIDEgMi42OSA2LjU3bC0xLjM3IDMuMjc0YTUuMDIgNS4wMiAwIDAgMS02LjU3IDIuNjlsLTIuMDQyLS44NTVhMjUgMjUgMCAwIDEtNC4yOTUgNC4yNmwuODQgMi4wNWE1LjAyIDUuMDIgMCAwIDEtMi43NDIgNi41NDhsLTMuMjg1IDEuMzQ1YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtLjg0LTIuMDVhMjUgMjUgMCAwIDEtNi4wNDktLjAyMmwtLjg1NSAyLjA0MmE1LjAyIDUuMDIgMCAwIDEtNi41NyAyLjY5bC0zLjI3NC0xLjM3YTUuMDIgNS4wMiAwIDAgMS0yLjY5LTYuNTdsLjg1NS0yLjA0MmEyNSAyNSAwIDAgMS00LjI2LTQuMjk1bC0yLjA1Ljg0YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtMS4zNDUtMy4yODVhNS4wMiA1LjAyIDAgMCAxIDIuNzQyLTYuNTQ4bDIuMDUtLjg0YTI1IDI1IDAgMCAxIC4wMjItNi4wNDlsLTIuMDQyLS44NTVhNS4wMiA1LjAyIDAgMCAxLTIuNjktNi41N2wxLjM3LTMuMjc0YTUuMDIgNS4wMiAwIDAgMSA2LjU3LTIuNjlsMi4wNDIuODU1YTI1IDI1IDAgMCAxIDQuMjk1LTQuMjZsLS44NC0yLjA1YTUuMDIgNS4wMiAwIDAgMSAyLjc0Mi02LjU0OGwzLjI4NS0xLjM0NWE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsLjg0IDIuMDVhMjUgMjUgMCAwIDEgNi4wNDkuMDIybC44NTUtMi4wNDJhNS4wMiA1LjAyIDAgMCAxIDYuNTctMi42OWwzLjI3NCAxLjM3YTUuMDIgNS4wMiAwIDAgMSAyLjY5IDYuNTdsLS44NTUgMi4wNDJhMjUgMjUgMCAwIDEgNC4yNiA0LjI5NWwyLjA1LS44NGE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsMS4zNDUgMy4yODVhNS4wMiA1LjAyIDAgMCAxLTIuNzQyIDYuNTQ4bC0yLjA1Ljg0YTI1IDI1IDAgMCAxLS4wMjIgNi4wNDltLTM3LjQ5OC04LjMzOGMtMi44OCA2Ljg3Ny4zNiAxNC43ODcgNy4yMzcgMTcuNjY3czE0Ljc4Ny0uMzYgMTcuNjY3LTcuMjM3LS4zNi0xNC43ODctNy4yMzctMTcuNjY3LTE0Ljc4Ny4zNi0xNy42NjcgNy4yMzciIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjwvZz48L3N2Zz4=", + nob: "dpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0wIDc4Ljk3NFYwaDc4Ljk3NHY3OC45NzR6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTM3Ljk2MSAxMC44NDdhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDRWNS42OTJjMC0xLjE2My45NDItMi4xMDUgMi4xMDQtMi4xMDVoMy4wNTJjMS4xNjIgMCAyLjEwNC45NDIgMi4xMDQgMi4xMDV2My4wNTFhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQgMi4xMDV6bTAgNjQuNTRhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDV2LTMuMDUxYzAtMS4xNjIuOTQyLTIuMTA1IDIuMTA0LTIuMTA1aDMuMDUyYzEuMTYyIDAgMi4xMDQuOTQzIDIuMTA0IDIuMTA1djMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA0IDIuMTA1em0yMC42OTgtNTcuMjNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6TTEzLjAyMyA2My43OTNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6bTU1LjEwNC0yNS44MzJjMC0xLjE2Mi45NDItMi4xMDQgMi4xMDQtMi4xMDRoMy4wNTFjMS4xNjMgMCAyLjEwNS45NDIgMi4xMDUgMi4xMDR2My4wNTJhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDUgMi4xMDRoLTMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA1LTIuMTA0em0tNjQuNTQgMGMwLTEuMTYyLjk0Mi0yLjEwNCAyLjEwNS0yLjEwNGgzLjA1MWMxLjE2MiAwIDIuMTA1Ljk0MiAyLjEwNSAyLjEwNHYzLjA1MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNSAyLjEwNEg1LjY5MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNS0yLjEwNHptNTcuMjMgMjAuNjk4YTIuMTA1IDIuMTA1IDAgMCAxIDIuOTc2IDBsMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMCAyLjk3NmwtMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEtMi45NzYgMGwtMi4xNTgtMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NnpNMTUuMTgxIDEzLjAyM2EyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDBsLTIuMTU4LTIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAtMi45NzZ6bTguNDE2IDEwLjQzYzcuNTQ2LTcuNTQ3IDE5LjA2NS04LjcwMiAyNy44MjctMy40NjUtLjEyNS4wOS0xMy4yNjQgMTcuODcyLTEyLjEzMyAxOS4wMDNsMS4wMzcgMS4wMzdjMS4xMyAxLjEzIDE4LjkxMy0xMi4wMDggMTkuMDAzLTEyLjEzMyA1LjIzNyA4Ljc2MiA0LjA4MiAyMC4yOC0zLjQ2NSAyNy44MjgtOC45MTEgOC45MS0yMy4zNTkgOC45MS0zMi4yNyAwLTguOTEtOC45MTEtOC45MS0yMy4zNTkgMC0zMi4yNyIgZmlsbD0iI2ZmZiIvPjwvZz48L3N2Zz4=", + flag: "ZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48cGF0aCBkPSJNLjc1IDJhNi40NCA2LjQ0IDAgMCAxIDcuNjkgMGgwYTYuNDQgNi40NCAwIDAgMCA3LjY5IDB2MTAuNGE2LjQ0IDYuNDQgMCAwIDEtNy42OSAwaDBhNi40NCA2LjQ0IDAgMCAwLTcuNjkgMCIgc3R5bGU9ImZpbGw6IzRjYmY1NjtzdHJva2U6IzQ1OTkzZDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQiLz48cGF0aCBzdHlsZT0iZmlsbDojNGNiZjU2O3N0cm9rZTojNDU5OTNkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MS41cHgiIGQ9Ik0uNzUgMTYuNzV2LTE2Ii8+PC9zdmc+", + stop: "ZpZXdCb3g9IjAgMCAxNCAxNCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6I2VjNTk1OTtzdHJva2U6I2I4NDg0ODtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIGQ9Ik00LjMuNWg1LjRsMy44IDMuOHY1LjRsLTMuOCAzLjhINC4zTC41IDkuN1Y0LjN6Ii8+PC9zdmc+" }; - for (const key in extraIcons) extraIcons[key] = "data:image/svg+xml;base64,PHN2Zy" + extraIcons[key]; + for (const key in extraIcons) extraIcons[key] = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIH" + extraIcons[key]; // Modified Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) // uses MIT License @@ -35,6 +35,7 @@ const runtime = vm.runtime; const scratchAudio = runtime.audioEngine; + const ts3Data = Symbol("ts3Data"); const simpleEffects = [ "pitch", "detune", "speed", "pan", "gain", "distortion", "attack", "release" @@ -44,50 +45,9 @@ "highpass", "lowpass", "flanger", "compressor", "equalizer" ]; + let deltaTime = 0, prevFrameTime = 0; let soundBank = {}; let settings = { flagCtrl: false, canSave: false }; - const load = (storage) => { - if (storage === undefined) return; - settings = storage.settings; - soundBank = storage.bank; - for (const item in soundBank) { - const sound = soundBank[item]; - sound.loaded = false; - if (sound.isVanilla) { - const info = sound.src.substring(1, sound.src.lastIndexOf(".")).split("/"); - let target = info[0] === "Stage" ? runtime.getTargetForStage() : runtime.getSpriteTargetByName(info[0]); - if (target === undefined) { - alert(`Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}`); - continue; - } - - const scratchSound = target.sprite.sounds.find((i) => { return i.name === info[1] }); - if (scratchSound === undefined) { - alert(`Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}`); - continue; - } - const buffer = target.sprite.soundBank.soundPlayers[scratchSound.soundId].buffer; - const engine = new Pizzicato.Sound({ - source: "buffer", options: { buffer, attack: 0 } - }); - - engine.sourceNode = engine.getSourceNode(); - sound.context = engine; - sound.loaded = true; - } else { - const engine = new Pizzicato.Sound({ - source: "file", options: { path: sound.src, attack: 0 } - }, () => { - engine.sourceNode = engine.getSourceNode(); - sound.context = engine; - sound.loaded = true; - }); - } - } - }; - if (!Scratch.extensions.isPenguinMod) { - runtime.on("PROJECT_LOADED", () => { load(runtime.extensionStorage["SPtuneShark3"]) }) - } // Create an Event for when Pause Project is Activated // Save original function if it exists @@ -103,6 +63,49 @@ class SPtuneShark3 { constructor() { + this.loadStorage = function(storage) { + if (storage === undefined) return; + settings = storage.settings; + soundBank = storage.bank; + for (const item in soundBank) { + const sound = soundBank[item]; + sound.loaded = false; + if (sound.isVanilla) { + const info = sound.src.substring(1, sound.src.lastIndexOf(".")).split("/"); + let target = info[0] === "Stage" ? runtime.getTargetForStage() : runtime.getSpriteTargetByName(info[0]); + if (target === undefined) { + alert(`Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}`); + continue; + } + + const scratchSound = target.sprite.sounds.find((i) => { return i.name === info[1] }); + if (scratchSound === undefined) { + alert(`Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}`); + continue; + } + const buffer = target.sprite.soundBank.soundPlayers[scratchSound.soundId].buffer; + const engine = new Pizzicato.Sound({ + source: "buffer", options: { buffer, attack: 0 } + }); + + engine.sourceNode = engine.getSourceNode(); + sound.context = engine; + sound.loaded = true; + } else { + const engine = new Pizzicato.Sound({ + source: "file", options: { path: sound.src, attack: 0 } + }, () => { + engine.sourceNode = engine.getSourceNode(); + sound.context = engine; + sound.loaded = true; + }); + } + } + } + if (!Scratch.extensions.isPenguinMod) { + runtime.on("PROJECT_LOADED", () => this.loadStorage(runtime.extensionStorage["SPtuneShark3"])) + } + runtime.on("PROJECT_START", () => { if (settings.flagCtrl) this.ctrlSounds({ CONTROL: "stop" }); }); @@ -110,22 +113,35 @@ if (settings.flagCtrl) this.ctrlSounds({ CONTROL: "stop" }); }); runtime.on("BEFORE_EXECUTE", () => { + const now = performance.now(); + deltaTime = prevFrameTime === 0 ? 0 : (now - prevFrameTime) / 1000; + prevFrameTime = now; + const projectVal = scratchAudio.inputNode.gain.value; - Object.keys(soundBank).forEach(key => { - const bank = soundBank[key]; + Object.values(soundBank).forEach(bank => { if (bank.loaded) { const sound = bank.context; // Clamp Volume to Project Volume const curVol = Math.min(100, Math.max(0, bank.vol)) / 100; sound.volume = curVol * projectVal; - // Apply Speed Changes - if (bank.speed !== 1 && sound.playing) { - const lastplay = sound.lastTimePlayed; - const time = Math.abs(lastplay - sound.sourceNode.context.currentTime); - sound.stop(); - sound.play(0, time * bank.speed); - sound.lastTimePlayed = lastplay; - this.patchLinks(sound.sourceNode, bank); + + if (sound.playing) { + // Increment Current Time + const leng = sound.loop && bank.loopParm[1] ? bank.loopParm[1] : sound.sourceNode.buffer.duration; + let time = bank.currentTime; + time += deltaTime * bank.rate; + if (sound.loop) time = Math.max(0, time % (leng - bank.loopParm[0]) + 0.00001) + bank.loopParm[0]; + else time = Math.min(leng, Math.max(0, time)); + bank.currentTime = time; + + // Apply Speed Changes + if (bank.speed !== 1) { + const lastTime = bank.currentTime; + sound.stop(); + bank.currentTime = lastTime; + sound.play(0, lastTime); + this.patchLinks(sound.sourceNode, bank); + } } } }); @@ -500,7 +516,7 @@ blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - THRESH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, + THRESH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 }, KNEE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, ATTACK: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, RELEASE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, @@ -551,6 +567,49 @@ } // Helper Funcs + startHats(data) { + let newThreads = []; + runtime.allScriptsByOpcodeDo("SPtuneShark3_whenSound", (script, target) => { + const thread = runtime._pushThread(script.blockId, target); + thread[ts3Data] = data; + newThreads.push(thread); + }); + return newThreads; + } + + generateData(name, src, context, isVanilla) { + return { + name, src, context, isVanilla, effects: {}, loaded: true, reversed: false, + currentTime: 0, vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, rate: 1, + loopParm: [0, 0], overlap: false, overlays: [], isBind: false, binds: {}, + _cache: { loudness: {}, tone: {} } + }; + } + + getBPM(data, sampleRate) { + const peaks = []; + let lastPeakIndex = 0, max = 0.1; + for (let i = 0; i < data.length; i++) { if (data[i] > max) max = data[i] } + for (let i = 0; i < data.length; i++) { + if (data[i] > max - 0.1 && i - lastPeakIndex > sampleRate / 4) { + peaks.push(i); + lastPeakIndex = i; + } + } + const intervals = []; + for (let i = 1; i < peaks.length; i++) intervals.push(peaks[i] - peaks[i - 1]); + const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length; + const value = Math.round((sampleRate / avgInterval) * 60); + return isNaN(value) ? 0 : value; + } + + patchLinks(src, sound) { + src.playbackRate.value = sound.pitch; + src.detune.value = sound.detune; + src.gainSuccessor.gain.value = sound.gain; + if (src.loop) this.loopParams({ NAME: sound.name, START: sound.loopParm[0], END: sound.loopParm[1] }); + } + updateEffect(effect, sound, name, args) { delete args.NAME; delete args.TYPE; effect.arguments = args; // Match Original Values, not Converted @@ -604,23 +663,6 @@ } } - getBPM(data, sampleRate) { - const peaks = []; - let lastPeakIndex = 0, max = 0.1; - for (let i = 0; i < data.length; i++) { if (data[i] > max) max = data[i] } - for (let i = 0; i < data.length; i++) { - if (data[i] > max - 0.1 && i - lastPeakIndex > sampleRate / 4) { - peaks.push(i); - lastPeakIndex = i; - } - } - const intervals = []; - for (let i = 1; i < peaks.length; i++) intervals.push(peaks[i] - peaks[i - 1]); - const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length; - const value = Math.round((sampleRate / avgInterval) * 60); - return isNaN(value) ? 0 : value; - } - play(sound, atTime, con) { try { if (sound.playing && con.overlap) { @@ -628,23 +670,24 @@ const newName = `${con.name}_COPY_${Math.random()}`; soundBank[newName] = { ...sound, - context: clone, name: newName, loopParm: [0, 0], pauseTime: 0, - rateChange: [0,0], overlap: false, overlays: [], isBind: false, binds: {} + context: clone, name: newName, loopParm: [0, 0], + overlap: false, overlays: [], isBind: false, binds: {} }; - clone.play(); + clone.play(0, atTime); clone.sourceNode.playbackRate.value = con.pitch; clone.sourceNode.gainSuccessor.gain.value = con.gain; con.overlays.push(clone); clone.on("end", () => { delete soundBank[newName] }); } else { - con.rateChange = [0,0]; - sound.play(0, sound.loop ? con.loopParm[0] : atTime); + if (!sound.playing) con.currentTime = atTime; + sound.play(0, atTime); const srcNode = sound.sourceNode; this.patchLinks(srcNode, con); if (Object.keys(con.binds).length > 0) { Object.keys(con.binds).forEach(key => { const thisSound = con.binds[key]; const context = thisSound.context; + if (!context.playing) thisSound.currentTime = atTime; context.play(0, atTime); this.patchLinks(context.sourceNode, thisSound); }); @@ -662,68 +705,24 @@ const ctx = sound.context; const src = ctx.sourceNode; if (type === "stop") { + const lastTime = sound.currentTime; ctx.stop(); + sound.currentTime = lastTime; for (let i = 0; i < sound.overlays.length; i++) sound.overlays[i].stop(); this.startHats({ name: sound.name, type: "stops" }); } else if (type === "pause") { - sound.pauseTime = src.context.currentTime; ctx.pause(); for (let i = 0; i < sound.overlays.length; i++) sound.overlays[i].pause(); this.startHats({ name: sound.name, type: "stops" }); } else { this.startHats({ name: sound.name, type: "starts" }); - if (type === "play") ctx.play(); - else { - if (!ctx.paused) return; - const lastTime = this.currentTime(sound, ctx, src); - ctx.stop(); - ctx.play(0, lastTime); - return this.patchLinks(ctx.sourceNode, sound); - } - this.patchLinks(src, sound); - for (let i = 0; i < sound.overlays.length; i++) { - sound.overlays[i].play(); - this.patchLinks(sound.overlays[i].sourceNode, sound); - } - } - } - - currentTime(sound, ctx, src) { - if (!ctx.playing && !ctx.paused) return 0; - const rate = sound.pitch * sound.speed * Math.pow(2, sound.detune / 1200); - const leng = ctx.loop && sound.loopParm[1] ? sound.loopParm[1] : src.buffer.duration; - const loopStart = sound.loopParm[0]; - const now = ctx.paused ? sound.pauseTime : src.context.currentTime; - let time = now - ctx.lastTimePlayed; - time = sound.rateChange[1] + ((time - sound.rateChange[0]) * rate); - if (ctx.loop) return Math.max(0, time % (leng - loopStart) + 0.00001) + loopStart; - return Math.min(leng, Math.max(0, time)); - } - - patchLinks(src, sound, optShifters) { - if (optShifters) { - const ctx = sound.context; - if ( - optShifters[0] !== sound.pitch || optShifters[1] !== sound.detune || optShifters[2] !== sound.speed - ) sound.rateChange = [ - this.currentTime(sound, ctx, src), - (ctx.paused ? sound.pauseTime : src.context.currentTime) - ctx.lastTimePlayed - ]; + if (!ctx.paused) return; + const lastTime = sound.currentTime; + ctx.stop(); + sound.currentTime = lastTime; + ctx.play(0, lastTime); + this.patchLinks(ctx.sourceNode, sound); } - src.playbackRate.value = sound.pitch; - src.detune.value = sound.detune; - src.gainSuccessor.gain.value = sound.gain; - if (src.loop) this.loopParams({ NAME: sound.name, START: sound.loopParm[0], END: sound.loopParm[1] }); - } - - startHats(data) { - let newThreads = []; - runtime.allScriptsByOpcodeDo("SPtuneShark3_whenSound", (script, target) => { - const thread = runtime._pushThread(script.blockId, target); - thread.SPts3Data = data; - newThreads.push(thread); - }); - return newThreads; } // Block Funcs @@ -736,12 +735,10 @@ }, () => { try { engine.sourceNode = engine.getSourceNode(); - soundBank[args.NAME] = { - context: engine, name: args.NAME, src: args.URL, effects: {}, pauseTime: 0, - rateChange: [0,0], loaded: true, reversed: false, vol: 100, gain: 1, pitch: 1, - detune: 0, speed: 1, loopParm: [0, 0], overlap: false, overlays: [], - isBind: false, binds: {}, _cache: { loudness: {}, tone: {} }, isVanilla: false - }; + const bank = soundBank[args.NAME] = this.generateData(args.NAME, args.URL, engine, false); + engine.on("stop", () => { + bank.currentTime = engine.loop && bank.loopParm[1] ? bank.loopParm[1] : engine.sourceNode.buffer.duration; + }); resolve(); } catch { alert("Tune Shark V3 Cant Import this Sound, File may be Corrupted or Non-Existent"); @@ -764,12 +761,10 @@ }); // this part of the Library was modified to work like this engine.sourceNode = engine.getSourceNode(); - soundBank[args.NAME] = { - context: engine, name: args.NAME, src: sourceURL, - effects: {}, pauseTime: 0, rateChange: [0,0], loaded: true, reversed: false, vol: 100, - gain: 1, pitch: 1, detune: 0, speed: 1, loopParm: [0, 0], overlap: false, overlays: [], - isBind: false, binds: {}, _cache: { loudness: {}, tone: {} }, isVanilla: true - }; + const bank = soundBank[args.NAME] = this.generateData(args.NAME, sourceURL, engine, true); + engine.on("stop", () => { + bank.currentTime = engine.loop && bank.loopParm[1] ? bank.loopParm[1] : engine.sourceNode.buffer.duration; + }); } } @@ -858,8 +853,15 @@ toggleLoop(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; + const oldValue = sound.context.loop; sound.context.loop = args.TYPE === "on"; if (args.TYPE === "off") this.typeOverlay(sound, "stop"); + else if (!oldValue) { + const lastTime = sound.currentTime; + sound.context.stop(); + sound.currentTime = lastTime; + sound.context.play(0, lastTime); + } } toggleReverse(args) { @@ -914,7 +916,7 @@ } whenSound(args, util) { - const data = util.thread.SPts3Data ?? {}; + const data = util.thread[ts3Data] ?? {}; return args.CONTROL === data.type && args.NAME === data.name; } @@ -939,7 +941,7 @@ const src = sound.context.sourceNode; switch (args.PROP) { case "length": return src.buffer.duration; - case "current time": return this.currentTime(sound, sound.context, src); + case "current time": return sound.currentTime; case "estimated bpm": return this.getBPM(src.buffer.getChannelData(0), src.buffer.sampleRate); case "channels": return src.buffer.numberOfChannels; case "source": return sound.src; @@ -1033,7 +1035,6 @@ const ctx = sound.context; const effect = Cast.toString(args.EFFECT); const name = effect.toUpperCase(); - const ogShifters = [sound.pitch, sound.detune, sound.speed]; if (effect === "pitch") sound.pitch = 1; else if (effect === "detune") sound.detune = 0; else if (effect === "speed") sound.speed = 1; @@ -1044,14 +1045,15 @@ sound.pitch = 1; sound.detune = 0; sound.speed = 1; sound.gain = 1; sound.context.attack = 0; sound.context.release = 0; const effects = sound.effects; - Object.keys(effects).forEach(key => { ctx.removeEffect(effects[key]) }); + Object.values(effects).forEach(e => ctx.removeEffect(e)); sound.effects = {}; } if (sound.effects[name] !== undefined) { ctx.removeEffect(sound.effects[name]); delete sound.effects[name]; } - this.patchLinks(ctx.sourceNode, sound, ogShifters); + sound.rate = sound.pitch * sound.speed * Math.pow(2, sound.detune / 1200); + this.patchLinks(ctx.sourceNode, sound); } setThingNew(args) { @@ -1059,7 +1061,6 @@ if (sound === undefined) return; const ctx = sound.context; const value = Cast.toNumber(args.VALUE) / 100; - const ogShifters = [sound.pitch, sound.detune, sound.speed]; if (args.TYPE === "pitch") sound.pitch = Math.max(0, value + 1); else if (args.TYPE === "detune") sound.detune = value * 1000; else if (args.TYPE === "speed") sound.speed = Math.max(0, value); @@ -1073,7 +1074,8 @@ const distort = new Pizzicato.Effects.Distortion({ gain: value }); return this.updateEffect(distort, sound, "DISTORTION", args); } - this.patchLinks(ctx.sourceNode, sound, ogShifters); + sound.rate = sound.pitch * sound.speed * Math.pow(2, sound.detune / 1200); + this.patchLinks(ctx.sourceNode, sound); } setReverb(args) { @@ -1200,7 +1202,7 @@ return { SPtuneShark3: { bank: convertBank, settings } } } } - deserialize(data) { load(data.SPtuneShark3) } + deserialize(data) { this.loadStorage(data.SPtuneShark3) } } Scratch.extensions.register(new SPtuneShark3()); From 987d24761afdb0547bdd22fb4c7685db1502fa05 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:43:16 -0800 Subject: [PATCH 28/40] Update Tune-Shark-V3.js --- extensions/SharkPool/Tune-Shark-V3.js | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 9572927d36..52f34caef1 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -703,7 +703,6 @@ typeOverlay(sound, type) { const ctx = sound.context; - const src = ctx.sourceNode; if (type === "stop") { const lastTime = sound.currentTime; ctx.stop(); From 77c964305a717110fbac354898120c2240298045 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:33:10 -0800 Subject: [PATCH 29/40] Tune-Shark-V3 -- Hotfix --- extensions/SharkPool/Tune-Shark-V3.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 52f34caef1..2e20d457d1 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.4.2 +// Version V.3.4.21 (function (Scratch) { "use strict"; @@ -808,18 +808,17 @@ if (sound !== undefined) this.play(sound.context, time, sound); } - async playAndStop(args) { + playAndStop(args, util) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - const time = Cast.toNumber(args.TIME); - const max = Cast.toNumber(args.MAX); - this.play(sound.context, time, sound); - await new Promise((resolve) => { - setTimeout(() => { - this.typeOverlay(sound, "stop"); - resolve(); - }, (max - time) * 1000); - }); + if (util.stackFrame.awaitingSound === undefined) { + util.stackFrame.awaitingSound = true; + this.play(sound.context, Cast.toNumber(args.TIME), sound); + util.yield(); + } else if (util.stackFrame.awaitingSound) { + if (sound.currentTime >= Cast.toNumber(args.MAX)) this.typeOverlay(sound, "stop"); + else util.yield(); + } } stopSound(args) { @@ -855,7 +854,7 @@ const oldValue = sound.context.loop; sound.context.loop = args.TYPE === "on"; if (args.TYPE === "off") this.typeOverlay(sound, "stop"); - else if (!oldValue) { + else if (!oldValue && sound.context.playing) { const lastTime = sound.currentTime; sound.context.stop(); sound.currentTime = lastTime; From b7e0861788cd44946dc848eeeefdcd7048e8c80a Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:40:36 -0800 Subject: [PATCH 30/40] Tune-Shark-V3 -- Pause Events are in Runtime now --- extensions/SharkPool/Tune-Shark-V3.js | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 2e20d457d1..8a190515db 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,7 +4,7 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.4.21 +// Version V.3.4.22 (function (Scratch) { "use strict"; @@ -49,18 +49,6 @@ let soundBank = {}; let settings = { flagCtrl: false, canSave: false }; - // Create an Event for when Pause Project is Activated - // Save original function if it exists - let ogPauseFunc = Object.getOwnPropertyDescriptor(runtime.ioDevices.clock, "_paused")?.set; - Object.defineProperty(runtime.ioDevices.clock, "_paused", { - set: function(value) { - this._pausedValue = value; - runtime.emit("SP_TUNE3_PROJECT_PAUSE", value); - if (ogPauseFunc) ogPauseFunc.call(this, value); - }, - get: function() { return this._pausedValue } - }); - class SPtuneShark3 { constructor() { this.loadStorage = function(storage) { @@ -146,10 +134,8 @@ } }); }); - runtime.on("SP_TUNE3_PROJECT_PAUSE", () => { - if (runtime.ioDevices.clock._paused) this.ctrlSounds({ CONTROL: "pause" }); - else this.ctrlSounds({ CONTROL: "unpause" }); - }); + runtime.on("RUNTIME_PAUSED", () => this.ctrlSounds({ CONTROL: "pause" })); + runtime.on("RUNTIME_UNPAUSED", () => this.ctrlSounds({ CONTROL: "unpause" })); } getInfo() { return { From 567d71452bf42a188e935fb95c5f4a0593697e65 Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Thu, 16 Jan 2025 12:57:57 +0000 Subject: [PATCH 31/40] [Automated] Format code --- extensions/SharkPool/Tune-Shark-V3.js | 795 +++++++++++++++++++------- 1 file changed, 574 insertions(+), 221 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 8a190515db..431afcf0db 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -8,26 +8,29 @@ (function (Scratch) { "use strict"; - if (!Scratch.extensions.unsandboxed) throw new Error("Tune Shark V3 must be run unsandboxed"); + if (!Scratch.extensions.unsandboxed) + throw new Error("Tune Shark V3 must be run unsandboxed"); const menuIconURI = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDIuMTg1IiBoZWlnaHQ9IjEwMi4xODUiIHZpZXdCb3g9IjAgMCAxMDIuMTg1IDEwMi4xODUiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgNTEuMDkzQzAgMjIuODc1IDIyLjg3NSAwIDUxLjA5MyAwczUxLjA5MyAyMi44NzUgNTEuMDkzIDUxLjA5My0yMi44NzUgNTEuMDkzLTUxLjA5MyA1MS4wOTNTMCA3OS4zMTEgMCA1MS4wOTMiIGZpbGw9IiM0MDQwNDAiLz48cGF0aCBkPSJNNC44NiA1MS4wOTNjMC0yNS41MzQgMjAuNy00Ni4yMzMgNDYuMjMzLTQ2LjIzMyAyNS41MzQgMCA0Ni4yMzMgMjAuNyA0Ni4yMzMgNDYuMjMzIDAgMjUuNTM0LTIwLjcgNDYuMjMzLTQ2LjIzMyA0Ni4yMzMtMjUuNTM0IDAtNDYuMjMzLTIwLjctNDYuMjMzLTQ2LjIzMyIgZmlsbD0iIzY2NiIvPjxwYXRoIGQ9Ik03Mi44MzcgODYuNjQzdi0uMDAzYy0xLjI1NCAyLjUzNi00LjY2OCAzLjkzNS04LjI2NCAzLjE5Ny00LjExOC0uODQ0LTYuOTE1LTQuMTctNi4yNDYtNy40MjguNjY4LTMuMjYgNC41NDgtNS4yMTYgOC42NjYtNC4zNzEgMS44NzUuMzg0IDMuNDc0IDEuMjg0IDQuNiAyLjQ1N2w2LjY4My0xNC4xNzhjLTEwLjU2Ni00LjEzNS0xOS43Ni01LjA5Ni0xOS43Ni01LjA5NmwtOC45ODcgMTkuMDYxYy0uOTY2IDIuOTI3LTQuNjM2IDQuNjIyLTguNTIgMy44MjYtNC4xMTctLjg0NC02LjkxNC00LjE3LTYuMjQ2LTcuNDMuNjY5LTMuMjU4IDQuNTQ4LTUuMjE0IDguNjY3LTQuMzcgMS45MS4zOTEgMy41MzYgMS4zMTcgNC42NjQgMi41MjJsMTIuMDM1LTI1LjUwN3MxMy41MzIuMjM2IDI2Ljk0NyA3LjExNHoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtMjguMzA5IDMwLjgzMSA0LjA0MyAyMy42ODQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIi8+PHBhdGggZD0iTTM0LjAzMyA1NS4yMTljMS4zOCAyLjYwNi0uNzcyIDYuMDQtNC44MDggNy42Ny00LjAzNyAxLjYzLTguNDI5LjgzNy05LjgxLTEuNzctMS4zOC0yLjYwNi43NzItNi4wNCA0LjgwOS03LjY3IDQuMDM2LTEuNjI5IDguNDI4LS44MzcgOS44MDkgMS43N20tNy45NS0yNy4wNjhzOS43MDUtMS43MDQgMTIuMzYzIDIuNzdjMi4zNzUgNCAuMDcxIDguNjk1LjIxMiAxMC4xMjguMTQgMS40MzMgMi4xNzUgMS4xMDkgMi4xNzUgMS4xMDlsLS4wMTQgMS42NzRzLTIuODY0LjY2OS0zLjQxMi0xLjMyYy0uNTQ3LTEuOTg4LS41Ni01Ljk3OC0yLjgyMy04LjIyNy0yLjI2NS0yLjI1LTcuNTM3LS43NTktNy41MzctLjc1OSIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik02MC40MzYgMzUuMzcxYy0uMzMzIDIuMTE4LTIuMzUzIDMuMzA5LTQuMTg5IDQuMDAyLTEuNjUuNzI2LTMuNTUgMS4wMDgtNS4yNzYuMzctMS42MzEtLjM4MS0zLjE4OC0xLjgwNy0yLjk5NC0zLjU5OC4xNTQtMi4wODUgMS44OTYtMy44MjIgMy44NDItNC40MDUgMS45NC0uNzk5IDQuMjUzLS43MyA2LjEwNS4yNi45MTMuNDIxIDIuMTg5LTE0LjE0MiAzLjAzNS0yMC41ODMuMS0uNjQyIDIuNTg0LS40NyAyLjUxMy4xNTEgMCAwLTEuODU0IDE1LjUyMi0zLjAzNiAyMy44MDMiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4="; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDIuMTg1IiBoZWlnaHQ9IjEwMi4xODUiIHZpZXdCb3g9IjAgMCAxMDIuMTg1IDEwMi4xODUiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgNTEuMDkzQzAgMjIuODc1IDIyLjg3NSAwIDUxLjA5MyAwczUxLjA5MyAyMi44NzUgNTEuMDkzIDUxLjA5My0yMi44NzUgNTEuMDkzLTUxLjA5MyA1MS4wOTNTMCA3OS4zMTEgMCA1MS4wOTMiIGZpbGw9IiM0MDQwNDAiLz48cGF0aCBkPSJNNC44NiA1MS4wOTNjMC0yNS41MzQgMjAuNy00Ni4yMzMgNDYuMjMzLTQ2LjIzMyAyNS41MzQgMCA0Ni4yMzMgMjAuNyA0Ni4yMzMgNDYuMjMzIDAgMjUuNTM0LTIwLjcgNDYuMjMzLTQ2LjIzMyA0Ni4yMzMtMjUuNTM0IDAtNDYuMjMzLTIwLjctNDYuMjMzLTQ2LjIzMyIgZmlsbD0iIzY2NiIvPjxwYXRoIGQ9Ik03Mi44MzcgODYuNjQzdi0uMDAzYy0xLjI1NCAyLjUzNi00LjY2OCAzLjkzNS04LjI2NCAzLjE5Ny00LjExOC0uODQ0LTYuOTE1LTQuMTctNi4yNDYtNy40MjguNjY4LTMuMjYgNC41NDgtNS4yMTYgOC42NjYtNC4zNzEgMS44NzUuMzg0IDMuNDc0IDEuMjg0IDQuNiAyLjQ1N2w2LjY4My0xNC4xNzhjLTEwLjU2Ni00LjEzNS0xOS43Ni01LjA5Ni0xOS43Ni01LjA5NmwtOC45ODcgMTkuMDYxYy0uOTY2IDIuOTI3LTQuNjM2IDQuNjIyLTguNTIgMy44MjYtNC4xMTctLjg0NC02LjkxNC00LjE3LTYuMjQ2LTcuNDMuNjY5LTMuMjU4IDQuNTQ4LTUuMjE0IDguNjY3LTQuMzcgMS45MS4zOTEgMy41MzYgMS4zMTcgNC42NjQgMi41MjJsMTIuMDM1LTI1LjUwN3MxMy41MzIuMjM2IDI2Ljk0NyA3LjExNHoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtMjguMzA5IDMwLjgzMSA0LjA0MyAyMy42ODQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIi8+PHBhdGggZD0iTTM0LjAzMyA1NS4yMTljMS4zOCAyLjYwNi0uNzcyIDYuMDQtNC44MDggNy42Ny00LjAzNyAxLjYzLTguNDI5LjgzNy05LjgxLTEuNzctMS4zOC0yLjYwNi43NzItNi4wNCA0LjgwOS03LjY3IDQuMDM2LTEuNjI5IDguNDI4LS44MzcgOS44MDkgMS43N20tNy45NS0yNy4wNjhzOS43MDUtMS43MDQgMTIuMzYzIDIuNzdjMi4zNzUgNCAuMDcxIDguNjk1LjIxMiAxMC4xMjguMTQgMS40MzMgMi4xNzUgMS4xMDkgMi4xNzUgMS4xMDlsLS4wMTQgMS42NzRzLTIuODY0LjY2OS0zLjQxMi0xLjMyYy0uNTQ3LTEuOTg4LS41Ni01Ljk3OC0yLjgyMy04LjIyNy0yLjI2NS0yLjI1LTcuNTM3LS43NTktNy41MzctLjc1OSIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik02MC40MzYgMzUuMzcxYy0uMzMzIDIuMTE4LTIuMzUzIDMuMzA5LTQuMTg5IDQuMDAyLTEuNjUuNzI2LTMuNTUgMS4wMDgtNS4yNzYuMzctMS42MzEtLjM4MS0zLjE4OC0xLjgwNy0yLjk5NC0zLjU5OC4xNTQtMi4wODUgMS44OTYtMy44MjIgMy44NDItNC40MDUgMS45NC0uNzk5IDQuMjUzLS43MyA2LjEwNS4yNi45MTMuNDIxIDIuMTg5LTE0LjE0MiAzLjAzNS0yMC41ODMuMS0uNjQyIDIuNTg0LS40NyAyLjUxMy4xNTEgMCAwLTEuODU0IDE1LjUyMi0zLjAzNiAyMy44MDMiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4="; const blockIconURI = -"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIj48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Im02MS4yMDIgNTMuODM2LS4wMDItLjAwM2MtLjA2OCAzLjcxLTMuMzM3IDcuMjgtOC4wMTYgOC40MTYtNS4zNTkgMS4zMDItMTAuNTM4LTEuMDgtMTEuNTY4LTUuMzJzMi40OC04LjczMyA3LjgzOS0xMC4wMzVjMi40NC0uNTkzIDQuODQtLjQyIDYuODMxLjM0MnYtMjAuNTZjLTE0Ljg1IDEuMDAzLTI2LjI5OCA1LjAwNi0yNi4yOTggNS4wMDZ2MjcuNjQ0Yy40OSA0LjAxNC0yLjkxNiA4LjA3OC03Ljk3IDkuMzA2LTUuMzU4IDEuMzAxLTEwLjUzNy0xLjA4MS0xMS41NjctNS4zMjFzMi40OC04LjczMyA3LjgzOC0xMC4wMzVjMi40ODYtLjYwNCA0LjkzMy0uNDE0IDYuOTQ2LjM4NGwuMDEyLTM2Ljk5OHMxNi4xOS03LjI5IDM1Ljk1NS02LjYzM3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIj48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Im02MS4yMDIgNTMuODM2LS4wMDItLjAwM2MtLjA2OCAzLjcxLTMuMzM3IDcuMjgtOC4wMTYgOC40MTYtNS4zNTkgMS4zMDItMTAuNTM4LTEuMDgtMTEuNTY4LTUuMzJzMi40OC04LjczMyA3LjgzOS0xMC4wMzVjMi40NC0uNTkzIDQuODQtLjQyIDYuODMxLjM0MnYtMjAuNTZjLTE0Ljg1IDEuMDAzLTI2LjI5OCA1LjAwNi0yNi4yOTggNS4wMDZ2MjcuNjQ0Yy40OSA0LjAxNC0yLjkxNiA4LjA3OC03Ljk3IDkuMzA2LTUuMzU4IDEuMzAxLTEwLjUzNy0xLjA4MS0xMS41NjctNS4zMjFzMi40OC04LjczMyA3LjgzOC0xMC4wMzVjMi40ODYtLjYwNCA0LjkzMy0uNDE0IDYuOTQ2LjM4NGwuMDEyLTM2Ljk5OHMxNi4xOS03LjI5IDM1Ljk1NS02LjYzM3oiIGZpbGw9IiNmZmYiLz48L2c+PC9zdmc+"; const extraIcons = { set: "dpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiID48cGF0aCBkPSJtNjQuNTMzIDQyLjYxIDIuMDQyLjg1NWE1LjAyIDUuMDIgMCAwIDEgMi42OSA2LjU3bC0xLjM3IDMuMjc0YTUuMDIgNS4wMiAwIDAgMS02LjU3IDIuNjlsLTIuMDQyLS44NTVhMjUgMjUgMCAwIDEtNC4yOTUgNC4yNmwuODQgMi4wNWE1LjAyIDUuMDIgMCAwIDEtMi43NDIgNi41NDhsLTMuMjg1IDEuMzQ1YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtLjg0LTIuMDVhMjUgMjUgMCAwIDEtNi4wNDktLjAyMmwtLjg1NSAyLjA0MmE1LjAyIDUuMDIgMCAwIDEtNi41NyAyLjY5bC0zLjI3NC0xLjM3YTUuMDIgNS4wMiAwIDAgMS0yLjY5LTYuNTdsLjg1NS0yLjA0MmEyNSAyNSAwIDAgMS00LjI2LTQuMjk1bC0yLjA1Ljg0YTUuMDIgNS4wMiAwIDAgMS02LjU0OC0yLjc0MmwtMS4zNDUtMy4yODVhNS4wMiA1LjAyIDAgMCAxIDIuNzQyLTYuNTQ4bDIuMDUtLjg0YTI1IDI1IDAgMCAxIC4wMjItNi4wNDlsLTIuMDQyLS44NTVhNS4wMiA1LjAyIDAgMCAxLTIuNjktNi41N2wxLjM3LTMuMjc0YTUuMDIgNS4wMiAwIDAgMSA2LjU3LTIuNjlsMi4wNDIuODU1YTI1IDI1IDAgMCAxIDQuMjk1LTQuMjZsLS44NC0yLjA1YTUuMDIgNS4wMiAwIDAgMSAyLjc0Mi02LjU0OGwzLjI4NS0xLjM0NWE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsLjg0IDIuMDVhMjUgMjUgMCAwIDEgNi4wNDkuMDIybC44NTUtMi4wNDJhNS4wMiA1LjAyIDAgMCAxIDYuNTctMi42OWwzLjI3NCAxLjM3YTUuMDIgNS4wMiAwIDAgMSAyLjY5IDYuNTdsLS44NTUgMi4wNDJhMjUgMjUgMCAwIDEgNC4yNiA0LjI5NWwyLjA1LS44NGE1LjAyIDUuMDIgMCAwIDEgNi41NDggMi43NDJsMS4zNDUgMy4yODVhNS4wMiA1LjAyIDAgMCAxLTIuNzQyIDYuNTQ4bC0yLjA1Ljg0YTI1IDI1IDAgMCAxLS4wMjIgNi4wNDltLTM3LjQ5OC04LjMzOGMtMi44OCA2Ljg3Ny4zNiAxNC43ODcgNy4yMzcgMTcuNjY3czE0Ljc4Ny0uMzYgMTcuNjY3LTcuMjM3LS4zNi0xNC43ODctNy4yMzctMTcuNjY3LTE0Ljc4Ny4zNi0xNy42NjcgNy4yMzciIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMCA3OC45NzRWMGg3OC45NzR2NzguOTc0eiIgZmlsbD0ibm9uZSIvPjwvZz48L3N2Zz4=", nob: "dpZHRoPSI3OC45NzQiIGhlaWdodD0iNzguOTc0IiB2aWV3Qm94PSIwIDAgNzguOTc0IDc4Ljk3NCI+PGcgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0wIDc4Ljk3NFYwaDc4Ljk3NHY3OC45NzR6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTM3Ljk2MSAxMC44NDdhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDRWNS42OTJjMC0xLjE2My45NDItMi4xMDUgMi4xMDQtMi4xMDVoMy4wNTJjMS4xNjIgMCAyLjEwNC45NDIgMi4xMDQgMi4xMDV2My4wNTFhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQgMi4xMDV6bTAgNjQuNTRhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDQtMi4xMDV2LTMuMDUxYzAtMS4xNjIuOTQyLTIuMTA1IDIuMTA0LTIuMTA1aDMuMDUyYzEuMTYyIDAgMi4xMDQuOTQzIDIuMTA0IDIuMTA1djMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA0IDIuMTA1em0yMC42OTgtNTcuMjNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6TTEzLjAyMyA2My43OTNhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NmwyLjE1OC0yLjE1OGEyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDB6bTU1LjEwNC0yNS44MzJjMC0xLjE2Mi45NDItMi4xMDQgMi4xMDQtMi4xMDRoMy4wNTFjMS4xNjMgMCAyLjEwNS45NDIgMi4xMDUgMi4xMDR2My4wNTJhMi4xMDUgMi4xMDUgMCAwIDEtMi4xMDUgMi4xMDRoLTMuMDUxYTIuMTA1IDIuMTA1IDAgMCAxLTIuMTA1LTIuMTA0em0tNjQuNTQgMGMwLTEuMTYyLjk0Mi0yLjEwNCAyLjEwNS0yLjEwNGgzLjA1MWMxLjE2MiAwIDIuMTA1Ljk0MiAyLjEwNSAyLjEwNHYzLjA1MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNSAyLjEwNEg1LjY5MmEyLjEwNSAyLjEwNSAwIDAgMS0yLjEwNS0yLjEwNHptNTcuMjMgMjAuNjk4YTIuMTA1IDIuMTA1IDAgMCAxIDIuOTc2IDBsMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMCAyLjk3NmwtMi4xNTggMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEtMi45NzYgMGwtMi4xNTgtMi4xNThhMi4xMDUgMi4xMDUgMCAwIDEgMC0yLjk3NnpNMTUuMTgxIDEzLjAyM2EyLjEwNSAyLjEwNSAwIDAgMSAyLjk3NiAwbDIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAgMi45NzZsLTIuMTU4IDIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxLTIuOTc2IDBsLTIuMTU4LTIuMTU4YTIuMTA1IDIuMTA1IDAgMCAxIDAtMi45NzZ6bTguNDE2IDEwLjQzYzcuNTQ2LTcuNTQ3IDE5LjA2NS04LjcwMiAyNy44MjctMy40NjUtLjEyNS4wOS0xMy4yNjQgMTcuODcyLTEyLjEzMyAxOS4wMDNsMS4wMzcgMS4wMzdjMS4xMyAxLjEzIDE4LjkxMy0xMi4wMDggMTkuMDAzLTEyLjEzMyA1LjIzNyA4Ljc2MiA0LjA4MiAyMC4yOC0zLjQ2NSAyNy44MjgtOC45MTEgOC45MS0yMy4zNTkgOC45MS0zMi4yNyAwLTguOTEtOC45MTEtOC45MS0yMy4zNTkgMC0zMi4yNyIgZmlsbD0iI2ZmZiIvPjwvZz48L3N2Zz4=", flag: "ZpZXdCb3g9IjAgMCAxNi42MyAxNy41Ij48cGF0aCBkPSJNLjc1IDJhNi40NCA2LjQ0IDAgMCAxIDcuNjkgMGgwYTYuNDQgNi40NCAwIDAgMCA3LjY5IDB2MTAuNGE2LjQ0IDYuNDQgMCAwIDEtNy42OSAwaDBhNi40NCA2LjQ0IDAgMCAwLTcuNjkgMCIgc3R5bGU9ImZpbGw6IzRjYmY1NjtzdHJva2U6IzQ1OTkzZDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQiLz48cGF0aCBzdHlsZT0iZmlsbDojNGNiZjU2O3N0cm9rZTojNDU5OTNkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MS41cHgiIGQ9Ik0uNzUgMTYuNzV2LTE2Ii8+PC9zdmc+", - stop: "ZpZXdCb3g9IjAgMCAxNCAxNCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6I2VjNTk1OTtzdHJva2U6I2I4NDg0ODtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIGQ9Ik00LjMuNWg1LjRsMy44IDMuOHY1LjRsLTMuOCAzLjhINC4zTC41IDkuN1Y0LjN6Ii8+PC9zdmc+" + stop: "ZpZXdCb3g9IjAgMCAxNCAxNCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggc3R5bGU9ImZpbGw6I2VjNTk1OTtzdHJva2U6I2I4NDg0ODtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTAiIGQ9Ik00LjMuNWg1LjRsMy44IDMuOHY1LjRsLTMuOCAzLjhINC4zTC41IDkuN1Y0LjN6Ii8+PC9zdmc+", }; - for (const key in extraIcons) extraIcons[key] = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIH" + extraIcons[key]; + for (const key in extraIcons) + extraIcons[key] = + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIH" + + extraIcons[key]; // Modified Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) // uses MIT License const scriptElement = document.createElement("script"); - scriptElement.textContent = -`!function(e){"use strict";var t={},i=t,n="object"==typeof module&&module.exports,o="function"==typeof define&&define.amd;n?module.exports=t:o?define([],t):e.Pizzicato=e.Pz=t;var s=e.AudioContext||e.webkitAudioContext;if(!s){console.error("No AudioContext found in this environment. Please ensure your window or global object contains a working AudioContext constructor function.");return}t.context=new s;var r=t.context.createGain();r.connect(t.context.destination),t.Util={isString:function(e){return"[object String]"===toString.call(e)},isObject:function(e){return"[object Object]"===toString.call(e)},isFunction:function(e){return"[object Function]"===toString.call(e)},isNumber:function(e){return"[object Number]"===toString.call(e)&&e===+e},isArray:function(e){return"[object Array]"===toString.call(e)},isInRange:function(e,t,n){return!!(i.Util.isNumber(e)&&i.Util.isNumber(t)&&i.Util.isNumber(n))&&e>=t&&e<=n},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof i.Sound},isEffect:function(e){for(var i in t.Effects)if(e instanceof t.Effects[i])return!0;return!1},normalize:function(e,t,n){if(i.Util.isNumber(e)&&i.Util.isNumber(t)&&i.Util.isNumber(n))return(n-t)*e/1+t},getDryLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e<=.5?1:1-(e-.5)*2},getWetLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e>=.5?1:1-(.5-e)*2}};var a=Object.getPrototypeOf(Object.getPrototypeOf(t.context.createGain())),c=a.connect;a.connect=function(e){var t=i.Util.isEffect(e)?e.inputNode:e;return c.call(this,t),e},Object.defineProperty(t,"volume",{enumerable:!0,get:function(){return r.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&r&&(r.gain.value=e)}}),Object.defineProperty(t,"masterGainNode",{enumerable:!1,get:function(){return r},set:function(e){console.error("Can't set the master gain node")}}),t.Events={on:function(e,t,i){e&&t&&(this._events=this._events||{},(this._events[e]||(this._events[e]=[])).push({callback:t,context:i||this,handler:this}))},trigger:function(e){var t,i,n,o;if(e){for(this._events=this._events||{},t=this._events[e]||(this._events[e]=[]),o=0,i=Math.max(0,arguments.length-1),n=[];o1){e.shift(),h(e,i);return}t=t||Error("Error decoding audio file "+e[0]),s.isFunction(i)&&i(t)}).bind(o))},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e[0]+". "+n.statusText)},n.send()}function u(e,i){var n=s.isFunction(e)?e:e.audioFunction,o=s.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!o)try{t.context.createScriptProcessor()}catch(r){o=2048}this.getRawSourceNode=function(){var e=t.context.createScriptProcessor(o,1,1);return e.onaudioprocess=n,e}}this.detached=a&&e.options.detached,this.masterVolume=t.context.createGain(),this.fadeNode=t.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(t.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=a&&e.options.loop,this.attack=a&&s.isNumber(e.options.attack)?e.options.attack:.04,this.volume=a&&s.isNumber(e.options.volume)?e.options.volume:1,a&&s.isNumber(e.options.release)?this.release=e.options.release:a&&s.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=.04,e?s.isString(e)?h.bind(this)(e,n):s.isFunction(e)?u.bind(this)(e,n):"file"===e.source?h.bind(this)(e.options.path,n):"buffer"===e.source?(function e(i,n){if((i=Array.isArray(i)?i:[i])[0]instanceof AudioBuffer){let s=i[0];o.getRawSourceNode=function(){let e=t.context.createBufferSource();return e.loop=this.loop,e.buffer=s,e},"function"==typeof n&&n()}}).bind(this)(e.options.buffer,n):"wave"===e.source?c.bind(this)(e.options,n):"input"===e.source?(function e(i,n){if(navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,!navigator.getUserMedia&&(!navigator.mediaDevices||navigator.mediaDevices.getUserMedia)){console.error("Your browser does not support getUserMedia. Note that the current document must be loaded securely for this to work");return}var r=(function(e){o.getRawSourceNode=function(){return t.context.createMediaStreamSource(e)},s.isFunction(n)&&n()}).bind(o),a=function(e){s.isFunction(n)&&n(e)};navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia({audio:!0}).then(r).catch(a):navigator.getUserMedia({audio:!0},r,a)}).bind(this)(e,n):"script"===e.source?u.bind(this)(e.options,n):"sound"===e.source&&(function e(t,n){this.getRawSourceNode=t.sound.getRawSourceNode,t.sound.sourceNode&&i.Util.isOscillator(t.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=t.sound.frequency)}).bind(this)(e.options,n):c.bind(this)({},n)},t.Sound.prototype=Object.create(t.Events,{play:{enumerable:!0,value:function(e,n){this.playing||(i.Util.isNumber(n)||(n=this.offsetTime||0),i.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),i.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=t.context.currentTime-n,this.sourceNode.start(i.context.currentTime+e,n)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=i.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/i.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new t.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),i=0;i0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this sound."),this;var n=this.playing;n&&this.pause();var o=0===i?this.fadeNode:this.effectConnectors[i-1];o.disconnect();var s=this.effectConnectors[i];return s.disconnect(),e.disconnect(s),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],o.connect(t),n&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.attack){this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,.001);return}var e=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,t=this.attack;e||(t=(1-this.fadeNode.gain.value)*this.attack),this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,2*t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,n=function(){return i.Util.isFunction(t.stop)?t.stop(0):t.disconnect()};if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.release){this.fadeNode.gain.setTargetAtTime(0,i.context.currentTime,.001),n();return}var o=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,s=this.release;o||(s=this.fadeNode.gain.value*this.release),this.fadeNode.gain.setTargetAtTime(1e-5,i.context.currentTime,s/5),window.setTimeout(function(){n()},1e3*s)}}}),t.Group=function(e){e=e||[],this.mergeGainNode=i.context.createGain(),this.masterVolume=i.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(i.masterGainNode);for(var t=0;t-1){console.warn("The Pizzicato.Sound object was already added to this group");return}if(e.detached){console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together.");return}e.disconnect(i.masterGainNode),e.connect(this.mergeGainNode),this.sounds.push(e)}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);if(-1===t){console.warn("Cannot remove a sound that is not part of this group.");return}e.disconnect(this.mergeGainNode),e.connect(i.masterGainNode),this.sounds.splice(t,1)}},volume:{enumerable:!0,get:function(){if(this.masterVolume)return this.masterVolume.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this group."),this;var n=0===i?this.mergeGainNode:this.effectConnectors[i-1];n.disconnect();var o=this.effectConnectors[i];return o.disconnect(),e.disconnect(o),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],n.connect(t),this}}}),t.Effects={};var h=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});function u(e,n){this.options={},e=e||this.options;var o={frequency:350,peak:1};for(var s in this.inputNode=this.filterNode=i.context.createBiquadFilter(),this.filterNode.type=n,this.outputNode=t.context.createGain(),this.filterNode.connect(this.outputNode),o)this[s]=e[s],this[s]=void 0===this[s]||null===this[s]?o[s]:this[s]}t.Effects.Delay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Delay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Compressor=function(e){this.options={},e=e||this.options;var i={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};for(var n in this.inputNode=this.compressorNode=t.context.createDynamicsCompressor(),this.outputNode=t.context.createGain(),this.compressorNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Compressor.prototype=Object.create(h,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){t.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){t.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){t.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),t.Effects.LowPassFilter=function(e){u.call(this,e,"lowpass")},t.Effects.HighPassFilter=function(e){u.call(this,e,"highpass")};var d=Object.create(h,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){t.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});function l(){var e,n,o=i.context.sampleRate*this.time,s=t.context.createBuffer(2,o,i.context.sampleRate),r=s.getChannelData(0),a=s.getChannelData(1);for(n=0;n-1?this.pannerNode.pan.value=e:this.pannerNode.setPosition(e,0,1-Math.abs(e))))}}}),t.Effects.Convolver=function(e,n){this.options={},e=e||this.options;var o=this,s=new XMLHttpRequest,r={mix:.5};for(var a in this.callback=n,this.inputNode=t.context.createGain(),this.convolverNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),r)this[a]=e[a],this[a]=void 0===this[a]||null===this[a]?r[a]:this[a];if(!e.impulse){console.error("No impulse file specified.");return}s.open("GET",e.impulse,!0),s.responseType="arraybuffer",s.onload=function(e){var n=e.target.response;t.context.decodeAudioData(n,function(e){o.convolverNode.buffer=e,o.callback&&i.Util.isFunction(o.callback)&&o.callback()},function(e){e=e||Error("Error decoding impulse file"),o.callback&&i.Util.isFunction(o.callback)&&o.callback(e)})},s.onreadystatechange=function(t){4===s.readyState&&200!==s.status&&console.error("Error while fetching "+e.impulse+". "+s.statusText)},s.send()},t.Effects.Convolver.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}}}),t.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.delayNodeLeft=t.context.createDelay(),this.delayNodeRight=t.context.createDelay(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.channelMerger=t.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.PingPongDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Reverb=function(e){this.options={},e=e||this.options;var i={mix:.5,time:.01,decay:.01,reverse:!1};for(var n in this.inputNode=t.context.createGain(),this.reverbNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n];l.bind(this)()},t.Effects.Reverb.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.time=e,l.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,l.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){i.Util.isBool(e)&&(this.options.reverse=e,l.bind(this)())}}}),t.Effects.Bitcrusher=function(e){this.inputNode=i.context.createGain(),this.outputNode=i.context.createGain(),this.bits=e.bits||4,this.frequency=e.frequency||44100,this.crusherNode=i.context.createScriptProcessor(4096,1,1);var t=this;this.crusherNode.onaudioprocess=function(e){for(var n=e.inputBuffer,o=e.outputBuffer,s=0;s=1&&e<=16&&(this.bits=e)},getBits:function(){return this.bits},setFrequency:function(e){e>0&&(this.frequency=e)},getFrequency:function(){return this.frequency}},t.Effects.ThreeBandEqualizer=function(e){f.call(this,e)};var p=Object.create(h,{cutoff_frequency_low:{enumerable:!0,get:function(){return this.options.cutoff_frequency_low},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_low=e,this.lowFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},cutoff_frequency_high:{enumerable:!0,get:function(){return this.options.cutoff_frequency_high},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_high=e,this.highFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},low_band_gain:{enumerable:!0,get:function(){return this.options.low_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.low_band_gain=e,this.lowGainNode.gain.value=Math.pow(10,e/20))}},mid_band_gain:{enumerable:!0,get:function(){return this.options.mid_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.mid_band_gain=e,this.midGainNode.gain.value=Math.pow(10,e/20))}},high_band_gain:{enumerable:!0,get:function(){return this.options.high_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.high_band_gain=e,this.highGainNode.gain.value=Math.pow(10,e/20))}},low_peak:{enumerable:!0,get:function(){return this.lowFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.lowFilterNode.Q.value=e)}},mid_peak:{enumerable:!0,get:function(){return this.midFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.midFilterNode.Q.value=e)}},high_peak:{enumerable:!0,get:function(){return this.highFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.highFilterNode.Q.value=e)}},visualizerBinCount:{enumerable:!0,get:function(){return this.analyserNode.frequencyBinCount},set:function(e){t.Util.isInRange(e,16,1024)&&(this.analyzerNode.fftSize=e)}},analyser:{enumerable:!0,get:function(){return this.analyserNode}},frequencyData:{enumerable:!0,get:function(){return void 0===this.byteFrequencyData&&(this.byteFrequencyData=new Uint8Array(this.analyserNode.frequencyBinCount.value)),this.analyserNode.getByteFrequencyData(this.FrequencyData),this.byteFrequencyData}}});t.Effects.ThreeBandEqualizer.prototype=p,t.Effects.Tremolo=function(e){this.options={},e=e||this.options;var i={speed:4,depth:1,mix:.8};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.tremoloGainNode=t.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=t.context.createOscillator(),this.shaperNode=t.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Tremolo.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){i.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),t.Effects.DubDelay=function(e){this.options={},e=e||this.options;var i={feedback:.6,time:.7,mix:.5,cutoff:700};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.bqFilterNode=t.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.DubDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){i.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),t.Effects.RingModulator=function(e){this.options={},e=e||this.options;var i={speed:30,distortion:1,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.vIn=t.context.createOscillator(),this.vIn.start(0),this.vInGain=t.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=t.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=t.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new N(t.context),this.vInDiode2=new N(t.context),this.vInInverter3=t.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=t.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new N(t.context),this.vcDiode4=new N(t.context),this.outGain=t.context.createGain(),this.outGain.gain.value=3,this.compressor=t.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]};var N=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};function v(e){for(var t=i.context.sampleRate,n=new Float32Array(t),o=Math.PI/180,s=0;sr;e=0<=r?++s:--s)n=(i=Math.abs(i=(e-t/2)/(t/2)))<=this.vb?0:this.vb=t&&e<=n},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof i.Sound},isEffect:function(e){for(var i in t.Effects)if(e instanceof t.Effects[i])return!0;return!1},normalize:function(e,t,n){if(i.Util.isNumber(e)&&i.Util.isNumber(t)&&i.Util.isNumber(n))return(n-t)*e/1+t},getDryLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e<=.5?1:1-(e-.5)*2},getWetLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e>=.5?1:1-(.5-e)*2}};var a=Object.getPrototypeOf(Object.getPrototypeOf(t.context.createGain())),c=a.connect;a.connect=function(e){var t=i.Util.isEffect(e)?e.inputNode:e;return c.call(this,t),e},Object.defineProperty(t,"volume",{enumerable:!0,get:function(){return r.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&r&&(r.gain.value=e)}}),Object.defineProperty(t,"masterGainNode",{enumerable:!1,get:function(){return r},set:function(e){console.error("Can't set the master gain node")}}),t.Events={on:function(e,t,i){e&&t&&(this._events=this._events||{},(this._events[e]||(this._events[e]=[])).push({callback:t,context:i||this,handler:this}))},trigger:function(e){var t,i,n,o;if(e){for(this._events=this._events||{},t=this._events[e]||(this._events[e]=[]),o=0,i=Math.max(0,arguments.length-1),n=[];o1){e.shift(),h(e,i);return}t=t||Error("Error decoding audio file "+e[0]),s.isFunction(i)&&i(t)}).bind(o))},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e[0]+". "+n.statusText)},n.send()}function u(e,i){var n=s.isFunction(e)?e:e.audioFunction,o=s.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!o)try{t.context.createScriptProcessor()}catch(r){o=2048}this.getRawSourceNode=function(){var e=t.context.createScriptProcessor(o,1,1);return e.onaudioprocess=n,e}}this.detached=a&&e.options.detached,this.masterVolume=t.context.createGain(),this.fadeNode=t.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(t.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=a&&e.options.loop,this.attack=a&&s.isNumber(e.options.attack)?e.options.attack:.04,this.volume=a&&s.isNumber(e.options.volume)?e.options.volume:1,a&&s.isNumber(e.options.release)?this.release=e.options.release:a&&s.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=.04,e?s.isString(e)?h.bind(this)(e,n):s.isFunction(e)?u.bind(this)(e,n):"file"===e.source?h.bind(this)(e.options.path,n):"buffer"===e.source?(function e(i,n){if((i=Array.isArray(i)?i:[i])[0]instanceof AudioBuffer){let s=i[0];o.getRawSourceNode=function(){let e=t.context.createBufferSource();return e.loop=this.loop,e.buffer=s,e},"function"==typeof n&&n()}}).bind(this)(e.options.buffer,n):"wave"===e.source?c.bind(this)(e.options,n):"input"===e.source?(function e(i,n){if(navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,!navigator.getUserMedia&&(!navigator.mediaDevices||navigator.mediaDevices.getUserMedia)){console.error("Your browser does not support getUserMedia. Note that the current document must be loaded securely for this to work");return}var r=(function(e){o.getRawSourceNode=function(){return t.context.createMediaStreamSource(e)},s.isFunction(n)&&n()}).bind(o),a=function(e){s.isFunction(n)&&n(e)};navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia({audio:!0}).then(r).catch(a):navigator.getUserMedia({audio:!0},r,a)}).bind(this)(e,n):"script"===e.source?u.bind(this)(e.options,n):"sound"===e.source&&(function e(t,n){this.getRawSourceNode=t.sound.getRawSourceNode,t.sound.sourceNode&&i.Util.isOscillator(t.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=t.sound.frequency)}).bind(this)(e.options,n):c.bind(this)({},n)},t.Sound.prototype=Object.create(t.Events,{play:{enumerable:!0,value:function(e,n){this.playing||(i.Util.isNumber(n)||(n=this.offsetTime||0),i.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),i.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=t.context.currentTime-n,this.sourceNode.start(i.context.currentTime+e,n)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=i.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/i.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new t.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),i=0;i0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this sound."),this;var n=this.playing;n&&this.pause();var o=0===i?this.fadeNode:this.effectConnectors[i-1];o.disconnect();var s=this.effectConnectors[i];return s.disconnect(),e.disconnect(s),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],o.connect(t),n&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.attack){this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,.001);return}var e=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,t=this.attack;e||(t=(1-this.fadeNode.gain.value)*this.attack),this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,2*t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,n=function(){return i.Util.isFunction(t.stop)?t.stop(0):t.disconnect()};if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.release){this.fadeNode.gain.setTargetAtTime(0,i.context.currentTime,.001),n();return}var o=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,s=this.release;o||(s=this.fadeNode.gain.value*this.release),this.fadeNode.gain.setTargetAtTime(1e-5,i.context.currentTime,s/5),window.setTimeout(function(){n()},1e3*s)}}}),t.Group=function(e){e=e||[],this.mergeGainNode=i.context.createGain(),this.masterVolume=i.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(i.masterGainNode);for(var t=0;t-1){console.warn("The Pizzicato.Sound object was already added to this group");return}if(e.detached){console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together.");return}e.disconnect(i.masterGainNode),e.connect(this.mergeGainNode),this.sounds.push(e)}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);if(-1===t){console.warn("Cannot remove a sound that is not part of this group.");return}e.disconnect(this.mergeGainNode),e.connect(i.masterGainNode),this.sounds.splice(t,1)}},volume:{enumerable:!0,get:function(){if(this.masterVolume)return this.masterVolume.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this group."),this;var n=0===i?this.mergeGainNode:this.effectConnectors[i-1];n.disconnect();var o=this.effectConnectors[i];return o.disconnect(),e.disconnect(o),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],n.connect(t),this}}}),t.Effects={};var h=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});function u(e,n){this.options={},e=e||this.options;var o={frequency:350,peak:1};for(var s in this.inputNode=this.filterNode=i.context.createBiquadFilter(),this.filterNode.type=n,this.outputNode=t.context.createGain(),this.filterNode.connect(this.outputNode),o)this[s]=e[s],this[s]=void 0===this[s]||null===this[s]?o[s]:this[s]}t.Effects.Delay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Delay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Compressor=function(e){this.options={},e=e||this.options;var i={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};for(var n in this.inputNode=this.compressorNode=t.context.createDynamicsCompressor(),this.outputNode=t.context.createGain(),this.compressorNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Compressor.prototype=Object.create(h,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){t.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){t.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){t.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),t.Effects.LowPassFilter=function(e){u.call(this,e,"lowpass")},t.Effects.HighPassFilter=function(e){u.call(this,e,"highpass")};var d=Object.create(h,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){t.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});function l(){var e,n,o=i.context.sampleRate*this.time,s=t.context.createBuffer(2,o,i.context.sampleRate),r=s.getChannelData(0),a=s.getChannelData(1);for(n=0;n-1?this.pannerNode.pan.value=e:this.pannerNode.setPosition(e,0,1-Math.abs(e))))}}}),t.Effects.Convolver=function(e,n){this.options={},e=e||this.options;var o=this,s=new XMLHttpRequest,r={mix:.5};for(var a in this.callback=n,this.inputNode=t.context.createGain(),this.convolverNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),r)this[a]=e[a],this[a]=void 0===this[a]||null===this[a]?r[a]:this[a];if(!e.impulse){console.error("No impulse file specified.");return}s.open("GET",e.impulse,!0),s.responseType="arraybuffer",s.onload=function(e){var n=e.target.response;t.context.decodeAudioData(n,function(e){o.convolverNode.buffer=e,o.callback&&i.Util.isFunction(o.callback)&&o.callback()},function(e){e=e||Error("Error decoding impulse file"),o.callback&&i.Util.isFunction(o.callback)&&o.callback(e)})},s.onreadystatechange=function(t){4===s.readyState&&200!==s.status&&console.error("Error while fetching "+e.impulse+". "+s.statusText)},s.send()},t.Effects.Convolver.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}}}),t.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.delayNodeLeft=t.context.createDelay(),this.delayNodeRight=t.context.createDelay(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.channelMerger=t.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.PingPongDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Reverb=function(e){this.options={},e=e||this.options;var i={mix:.5,time:.01,decay:.01,reverse:!1};for(var n in this.inputNode=t.context.createGain(),this.reverbNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n];l.bind(this)()},t.Effects.Reverb.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.time=e,l.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,l.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){i.Util.isBool(e)&&(this.options.reverse=e,l.bind(this)())}}}),t.Effects.Bitcrusher=function(e){this.inputNode=i.context.createGain(),this.outputNode=i.context.createGain(),this.bits=e.bits||4,this.frequency=e.frequency||44100,this.crusherNode=i.context.createScriptProcessor(4096,1,1);var t=this;this.crusherNode.onaudioprocess=function(e){for(var n=e.inputBuffer,o=e.outputBuffer,s=0;s=1&&e<=16&&(this.bits=e)},getBits:function(){return this.bits},setFrequency:function(e){e>0&&(this.frequency=e)},getFrequency:function(){return this.frequency}},t.Effects.ThreeBandEqualizer=function(e){f.call(this,e)};var p=Object.create(h,{cutoff_frequency_low:{enumerable:!0,get:function(){return this.options.cutoff_frequency_low},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_low=e,this.lowFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},cutoff_frequency_high:{enumerable:!0,get:function(){return this.options.cutoff_frequency_high},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_high=e,this.highFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},low_band_gain:{enumerable:!0,get:function(){return this.options.low_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.low_band_gain=e,this.lowGainNode.gain.value=Math.pow(10,e/20))}},mid_band_gain:{enumerable:!0,get:function(){return this.options.mid_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.mid_band_gain=e,this.midGainNode.gain.value=Math.pow(10,e/20))}},high_band_gain:{enumerable:!0,get:function(){return this.options.high_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.high_band_gain=e,this.highGainNode.gain.value=Math.pow(10,e/20))}},low_peak:{enumerable:!0,get:function(){return this.lowFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.lowFilterNode.Q.value=e)}},mid_peak:{enumerable:!0,get:function(){return this.midFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.midFilterNode.Q.value=e)}},high_peak:{enumerable:!0,get:function(){return this.highFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.highFilterNode.Q.value=e)}},visualizerBinCount:{enumerable:!0,get:function(){return this.analyserNode.frequencyBinCount},set:function(e){t.Util.isInRange(e,16,1024)&&(this.analyzerNode.fftSize=e)}},analyser:{enumerable:!0,get:function(){return this.analyserNode}},frequencyData:{enumerable:!0,get:function(){return void 0===this.byteFrequencyData&&(this.byteFrequencyData=new Uint8Array(this.analyserNode.frequencyBinCount.value)),this.analyserNode.getByteFrequencyData(this.FrequencyData),this.byteFrequencyData}}});t.Effects.ThreeBandEqualizer.prototype=p,t.Effects.Tremolo=function(e){this.options={},e=e||this.options;var i={speed:4,depth:1,mix:.8};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.tremoloGainNode=t.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=t.context.createOscillator(),this.shaperNode=t.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Tremolo.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){i.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),t.Effects.DubDelay=function(e){this.options={},e=e||this.options;var i={feedback:.6,time:.7,mix:.5,cutoff:700};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.bqFilterNode=t.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.DubDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){i.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),t.Effects.RingModulator=function(e){this.options={},e=e||this.options;var i={speed:30,distortion:1,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.vIn=t.context.createOscillator(),this.vIn.start(0),this.vInGain=t.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=t.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=t.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new N(t.context),this.vInDiode2=new N(t.context),this.vInInverter3=t.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=t.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new N(t.context),this.vcDiode4=new N(t.context),this.outGain=t.context.createGain(),this.outGain.gain.value=3,this.compressor=t.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]};var N=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};function v(e){for(var t=i.context.sampleRate,n=new Float32Array(t),o=Math.PI/180,s=0;sr;e=0<=r?++s:--s)n=(i=Math.abs(i=(e-t/2)/(t/2)))<=this.vb?0:this.vb { return i.name === info[1] }); + const scratchSound = target.sprite.sounds.find((i) => { + return i.name === info[1]; + }); if (scratchSound === undefined) { - alert(`Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}`); + alert( + `Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}` + ); continue; } - const buffer = target.sprite.soundBank.soundPlayers[scratchSound.soundId].buffer; + const buffer = + target.sprite.soundBank.soundPlayers[scratchSound.soundId].buffer; const engine = new Pizzicato.Sound({ - source: "buffer", options: { buffer, attack: 0 } + source: "buffer", + options: { buffer, attack: 0 }, }); engine.sourceNode = engine.getSourceNode(); sound.context = engine; sound.loaded = true; } else { - const engine = new Pizzicato.Sound({ - source: "file", options: { path: sound.src, attack: 0 } - }, () => { - engine.sourceNode = engine.getSourceNode(); - sound.context = engine; - sound.loaded = true; - }); + const engine = new Pizzicato.Sound( + { + source: "file", + options: { path: sound.src, attack: 0 }, + }, + () => { + engine.sourceNode = engine.getSourceNode(); + sound.context = engine; + sound.loaded = true; + } + ); } } - } + }; if (!Scratch.extensions.isPenguinMod) { - runtime.on("PROJECT_LOADED", () => this.loadStorage(runtime.extensionStorage["SPtuneShark3"])) + runtime.on("PROJECT_LOADED", () => + this.loadStorage(runtime.extensionStorage["SPtuneShark3"]) + ); } runtime.on("PROJECT_START", () => { @@ -106,7 +143,7 @@ prevFrameTime = now; const projectVal = scratchAudio.inputNode.gain.value; - Object.values(soundBank).forEach(bank => { + Object.values(soundBank).forEach((bank) => { if (bank.loaded) { const sound = bank.context; // Clamp Volume to Project Volume @@ -115,10 +152,16 @@ if (sound.playing) { // Increment Current Time - const leng = sound.loop && bank.loopParm[1] ? bank.loopParm[1] : sound.sourceNode.buffer.duration; + const leng = + sound.loop && bank.loopParm[1] + ? bank.loopParm[1] + : sound.sourceNode.buffer.duration; let time = bank.currentTime; time += deltaTime * bank.rate; - if (sound.loop) time = Math.max(0, time % (leng - bank.loopParm[0]) + 0.00001) + bank.loopParm[0]; + if (sound.loop) + time = + Math.max(0, (time % (leng - bank.loopParm[0])) + 0.00001) + + bank.loopParm[0]; else time = Math.min(leng, Math.max(0, time)); bank.currentTime = time; @@ -135,7 +178,9 @@ }); }); runtime.on("RUNTIME_PAUSED", () => this.ctrlSounds({ CONTROL: "pause" })); - runtime.on("RUNTIME_UNPAUSED", () => this.ctrlSounds({ CONTROL: "unpause" })); + runtime.on("RUNTIME_UNPAUSED", () => + this.ctrlSounds({ CONTROL: "unpause" }) + ); } getInfo() { return { @@ -151,8 +196,14 @@ text: "import sound from URL [URL] named [NAME]", blockIconURI: extraIcons.set, arguments: { - URL: { type: Scratch.ArgumentType.STRING, defaultValue: "https://extensions.turbowarp.org/meow.mp3" }, - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + URL: { + type: Scratch.ArgumentType.STRING, + defaultValue: "https://extensions.turbowarp.org/meow.mp3", + }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, }, }, { @@ -162,7 +213,10 @@ blockIconURI: extraIcons.set, arguments: { SOUND: { type: Scratch.ArgumentType.SOUND }, - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, }, }, { @@ -171,8 +225,14 @@ text: "convert sound [NAME1] from URL to URI and save to [NAME2]", blockIconURI: extraIcons.set, arguments: { - NAME1: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - NAME2: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound2" } + NAME1: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + NAME2: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound2", + }, }, }, { @@ -182,8 +242,14 @@ blockIconURI: extraIcons.set, arguments: { TYPE: { type: Scratch.ArgumentType.STRING, menu: "bindMenu" }, - NAME2: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound2" } + NAME2: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound2", + }, }, }, { @@ -192,7 +258,7 @@ text: "[SAVE] all sounds to project", blockIconURI: extraIcons.set, arguments: { - SAVE: { type: Scratch.ArgumentType.STRING, menu: "saveMenu" } + SAVE: { type: Scratch.ArgumentType.STRING, menu: "saveMenu" }, }, }, { blockType: Scratch.BlockType.LABEL, text: "Audio Playback" }, @@ -201,7 +267,10 @@ blockType: Scratch.BlockType.COMMAND, text: "start sound [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, }, }, { @@ -209,8 +278,11 @@ blockType: Scratch.BlockType.COMMAND, text: "start sound [NAME] at time [TIME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, }, }, { @@ -218,9 +290,12 @@ blockType: Scratch.BlockType.COMMAND, text: "start sound [NAME] at time [TIME] and stop at [MAX] seconds", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, - MAX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 } + MAX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 }, }, }, { @@ -228,7 +303,10 @@ blockType: Scratch.BlockType.COMMAND, text: "stop sound [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, }, }, { @@ -236,8 +314,14 @@ blockType: Scratch.BlockType.COMMAND, text: "[UN_PAUSE] sound [NAME]", arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - UN_PAUSE: { type: Scratch.ArgumentType.STRING, menu: "un_pauseMenu" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + UN_PAUSE: { + type: Scratch.ArgumentType.STRING, + menu: "un_pauseMenu", + }, }, }, "---", @@ -246,7 +330,7 @@ blockType: Scratch.BlockType.COMMAND, text: "[CONTROL] all sounds", arguments: { - CONTROL: { type: Scratch.ArgumentType.STRING, menu: "playMenu" } + CONTROL: { type: Scratch.ArgumentType.STRING, menu: "playMenu" }, }, }, { blockType: Scratch.BlockType.LABEL, text: "Audio Operations" }, @@ -256,10 +340,16 @@ text: "toggle sound link to [GO] [STOP] [ON_OFF]", blockIconURI: extraIcons.set, arguments: { - GO: { type: Scratch.ArgumentType.IMAGE, dataURI: extraIcons.flag }, - STOP: { type: Scratch.ArgumentType.IMAGE, dataURI: extraIcons.stop }, - ON_OFF: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } - } + GO: { + type: Scratch.ArgumentType.IMAGE, + dataURI: extraIcons.flag, + }, + STOP: { + type: Scratch.ArgumentType.IMAGE, + dataURI: extraIcons.stop, + }, + ON_OFF: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" }, + }, }, { opcode: "toggleOverlap", @@ -267,8 +357,11 @@ text: "toggle sound [NAME] overlapping [TYPE]", blockIconURI: extraIcons.set, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" }, }, }, { @@ -277,8 +370,11 @@ text: "toggle sound [NAME] reverse mode [TYPE]", blockIconURI: extraIcons.set, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" }, }, }, { @@ -287,8 +383,11 @@ text: "toggle sound [NAME] looping [TYPE]", blockIconURI: extraIcons.set, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" }, }, }, { @@ -297,9 +396,12 @@ text: "sound [NAME] loop start [START] end [END]", blockIconURI: extraIcons.set, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, START: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, - END: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 } + END: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 }, }, }, "---", @@ -309,26 +411,29 @@ text: "delete sound [NAME]", blockIconURI: extraIcons.set, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, }, }, { opcode: "deleteAllSounds", blockType: Scratch.BlockType.COMMAND, text: "delete all sounds", - blockIconURI: extraIcons.set + blockIconURI: extraIcons.set, }, { opcode: "allSounds", blockType: Scratch.BlockType.REPORTER, text: "all sounds", - blockIconURI: extraIcons.set + blockIconURI: extraIcons.set, }, { opcode: "allPlaySounds", blockType: Scratch.BlockType.REPORTER, text: "all playing sounds", - blockIconURI: extraIcons.set + blockIconURI: extraIcons.set, }, "---", { @@ -338,8 +443,11 @@ isEdgeActivated: false, blockIconURI: extraIcons.set, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - CONTROL: { type: Scratch.ArgumentType.STRING, menu: "hatPlayer" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + CONTROL: { type: Scratch.ArgumentType.STRING, menu: "hatPlayer" }, }, }, { @@ -348,8 +456,14 @@ text: "sound [NAME] [CONTROL] ?", blockIconURI: extraIcons.set, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - CONTROL: { type: Scratch.ArgumentType.STRING, menu: "soundBools" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + CONTROL: { + type: Scratch.ArgumentType.STRING, + menu: "soundBools", + }, }, }, { @@ -358,8 +472,11 @@ text: "[PROP] of sound [NAME]", blockIconURI: extraIcons.set, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - PROP: { type: Scratch.ArgumentType.STRING, menu: "soundProps" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + PROP: { type: Scratch.ArgumentType.STRING, menu: "soundProps" }, }, }, { @@ -369,9 +486,12 @@ blockIconURI: extraIcons.set, arguments: { TYPE: { type: Scratch.ArgumentType.STRING, menu: "loudProps" }, - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, - CHANNEL: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 } + CHANNEL: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, }, }, { blockType: Scratch.BlockType.LABEL, text: "Audio Effects" }, @@ -381,8 +501,11 @@ text: "set volume of sound [NAME] to [NUM]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, }, }, { @@ -392,7 +515,10 @@ blockIconURI: extraIcons.nob, arguments: { EFFECT: { type: Scratch.ArgumentType.STRING, menu: "effectMenu" }, - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, }, }, "---", @@ -402,9 +528,15 @@ text: "set [TYPE] of sound [NAME] to [VALUE]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, - TYPE: { type: Scratch.ArgumentType.STRING, menu: "singleEffectNew" }, - VALUE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 } + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "singleEffectNew", + }, + VALUE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, }, }, { @@ -413,10 +545,13 @@ text: "set reverb of sound [NAME] to time [TIME] decay [DECAY] mix [MIX]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, DECAY: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, - MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, }, }, { @@ -425,10 +560,13 @@ text: "set delay of sound [NAME] to time [TIME] feedback [FEED] mix [MIX]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, FEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, - MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, }, }, { @@ -437,10 +575,13 @@ text: "set tremolo of sound [NAME] to speed [SPEED] depth [DEPTH] mix [MIX]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, SPEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 35 }, DEPTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 80 }, - MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 } + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, }, }, "---", @@ -450,12 +591,15 @@ text: "set fuzz of sound [NAME] to low [LOW] med-low [MED1] med-high [MED2] high [HIGH] mix [MIX]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, LOW: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, MED1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 80 }, MED2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, HIGH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, - MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, }, }, { @@ -464,9 +608,12 @@ text: "set bitcrush of sound [NAME] bits [BITS] freq [FREQ]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, BITS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 65 }, - FREQ: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60000 } + FREQ: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60000 }, }, }, { @@ -475,10 +622,13 @@ text: "set [TYPE] of sound [NAME] to frequency [FREQ] peak [PEAK]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "typePass" }, FREQ: { type: Scratch.ArgumentType.NUMBER, defaultValue: 400 }, - PEAK: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 } + PEAK: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 }, }, }, { @@ -487,12 +637,15 @@ text: "set flanger of sound [NAME] to time [TIME] speed [SPEED] depth [DEPTH] feed [FEED] mix [MIX]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 45 }, SPEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 20 }, DEPTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 }, FEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 }, - MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + MIX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, }, }, { @@ -501,12 +654,15 @@ text: "set compressor of sound [NAME] to threshold [THRESH] knee [KNEE] attack [ATTACK] release [RELEASE] ratio [RATIO]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, THRESH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 }, KNEE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, ATTACK: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, RELEASE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, - RATIO: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + RATIO: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, }, }, { @@ -515,14 +671,17 @@ text: "set equalizer of sound [NAME] to gain low [LOW] mid [MID] high [HIGH] cutoff low [CUT_LOW] cutoff high [CUT_HIGH]", blockIconURI: extraIcons.nob, arguments: { - NAME: { type: Scratch.ArgumentType.STRING, defaultValue: "MySound" }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "MySound", + }, LOW: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, MID: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, HIGH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, CUT_LOW: { type: Scratch.ArgumentType.NUMBER, defaultValue: -50 }, - CUT_HIGH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 } + CUT_HIGH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, }, - } + }, ], menus: { saveMenu: ["save", "dont save"], @@ -537,17 +696,31 @@ soundProps: { acceptReporters: true, items: [ - "length", "current time", "source", "estimated bpm", "channels", "binds", "volume", - ].concat(simpleEffects, complexEffects) + "length", + "current time", + "source", + "estimated bpm", + "channels", + "binds", + "volume", + ].concat(simpleEffects, complexEffects), }, soundBools: { acceptReporters: true, - items: ["exists", "playing", "paused", "looped", "overlaped", "reversed", "binded"] + items: [ + "exists", + "playing", + "paused", + "looped", + "overlaped", + "reversed", + "binded", + ], }, effectMenu: { acceptReporters: true, - items: ["all effects"].concat(simpleEffects, complexEffects) - } + items: ["all effects"].concat(simpleEffects, complexEffects), + }, }, }; } @@ -555,27 +728,49 @@ // Helper Funcs startHats(data) { let newThreads = []; - runtime.allScriptsByOpcodeDo("SPtuneShark3_whenSound", (script, target) => { - const thread = runtime._pushThread(script.blockId, target); - thread[ts3Data] = data; - newThreads.push(thread); - }); + runtime.allScriptsByOpcodeDo( + "SPtuneShark3_whenSound", + (script, target) => { + const thread = runtime._pushThread(script.blockId, target); + thread[ts3Data] = data; + newThreads.push(thread); + } + ); return newThreads; } generateData(name, src, context, isVanilla) { return { - name, src, context, isVanilla, effects: {}, loaded: true, reversed: false, - currentTime: 0, vol: 100, gain: 1, pitch: 1, detune: 0, speed: 1, rate: 1, - loopParm: [0, 0], overlap: false, overlays: [], isBind: false, binds: {}, - _cache: { loudness: {}, tone: {} } + name, + src, + context, + isVanilla, + effects: {}, + loaded: true, + reversed: false, + currentTime: 0, + vol: 100, + gain: 1, + pitch: 1, + detune: 0, + speed: 1, + rate: 1, + loopParm: [0, 0], + overlap: false, + overlays: [], + isBind: false, + binds: {}, + _cache: { loudness: {}, tone: {} }, }; } getBPM(data, sampleRate) { const peaks = []; - let lastPeakIndex = 0, max = 0.1; - for (let i = 0; i < data.length; i++) { if (data[i] > max) max = data[i] } + let lastPeakIndex = 0, + max = 0.1; + for (let i = 0; i < data.length; i++) { + if (data[i] > max) max = data[i]; + } for (let i = 0; i < data.length; i++) { if (data[i] > max - 0.1 && i - lastPeakIndex > sampleRate / 4) { peaks.push(i); @@ -583,8 +778,10 @@ } } const intervals = []; - for (let i = 1; i < peaks.length; i++) intervals.push(peaks[i] - peaks[i - 1]); - const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length; + for (let i = 1; i < peaks.length; i++) + intervals.push(peaks[i] - peaks[i - 1]); + const avgInterval = + intervals.reduce((a, b) => a + b, 0) / intervals.length; const value = Math.round((sampleRate / avgInterval) * 60); return isNaN(value) ? 0 : value; } @@ -593,11 +790,17 @@ src.playbackRate.value = sound.pitch; src.detune.value = sound.detune; src.gainSuccessor.gain.value = sound.gain; - if (src.loop) this.loopParams({ NAME: sound.name, START: sound.loopParm[0], END: sound.loopParm[1] }); + if (src.loop) + this.loopParams({ + NAME: sound.name, + START: sound.loopParm[0], + END: sound.loopParm[1], + }); } updateEffect(effect, sound, name, args) { - delete args.NAME; delete args.TYPE; + delete args.NAME; + delete args.TYPE; effect.arguments = args; // Match Original Values, not Converted if (sound.effects[name] === undefined) { effect.id = name; @@ -607,7 +810,9 @@ // Dont Remove the Effect (Causes Glitches in Forever Loops), simply change each value // We use args just in case some Effects dont store them in the audio context const options = effect.options; - const thisEffect = sound.context.effects.find(effect => effect.id === name); + const thisEffect = sound.context.effects.find( + (effect) => effect.id === name + ); thisEffect.arguments = effect.arguments; thisEffect.options = options; switch (name) { @@ -616,7 +821,9 @@ thisEffect.pan = options.pan; return; } - case "DISTORTION": { return thisEffect.gain = options.gain } + case "DISTORTION": { + return (thisEffect.gain = options.gain); + } case "BITCRUSH": { thisEffect.frequency = Math.max(30000, Cast.toNumber(args.FREQ)); thisEffect.bits = Math.max(10, Cast.toNumber(args.BITS)) / 10; @@ -637,15 +844,30 @@ case "COMPRESSOR": { const node = thisEffect.compressorNode; const values = { - threshold: Math.min(0, Math.max(-100, Cast.toNumber(args.THRESH) * -1)), - ratio: Cast.toNumber(args.RATIO) / 5, attack: Math.min(0, Math.max(1, Cast.toNumber(args.ATTACK) / 100)), - release: Math.min(0, Math.max(1, Cast.toNumber(args.RELEASE) / 100)), knee: Cast.toNumber(args.KNEE) / 2.5 + threshold: Math.min( + 0, + Math.max(-100, Cast.toNumber(args.THRESH) * -1) + ), + ratio: Cast.toNumber(args.RATIO) / 5, + attack: Math.min( + 0, + Math.max(1, Cast.toNumber(args.ATTACK) / 100) + ), + release: Math.min( + 0, + Math.max(1, Cast.toNumber(args.RELEASE) / 100) + ), + knee: Cast.toNumber(args.KNEE) / 2.5, }; - Object.keys(values).forEach(key => { node[key].value = values[key] }); + Object.keys(values).forEach((key) => { + node[key].value = values[key]; + }); return; } } - Object.keys(options).forEach(key => { thisEffect[key] = options[key] }); + Object.keys(options).forEach((key) => { + thisEffect[key] = options[key]; + }); } } @@ -656,21 +878,28 @@ const newName = `${con.name}_COPY_${Math.random()}`; soundBank[newName] = { ...sound, - context: clone, name: newName, loopParm: [0, 0], - overlap: false, overlays: [], isBind: false, binds: {} + context: clone, + name: newName, + loopParm: [0, 0], + overlap: false, + overlays: [], + isBind: false, + binds: {}, }; clone.play(0, atTime); clone.sourceNode.playbackRate.value = con.pitch; clone.sourceNode.gainSuccessor.gain.value = con.gain; con.overlays.push(clone); - clone.on("end", () => { delete soundBank[newName] }); + clone.on("end", () => { + delete soundBank[newName]; + }); } else { if (!sound.playing) con.currentTime = atTime; sound.play(0, atTime); const srcNode = sound.sourceNode; this.patchLinks(srcNode, con); if (Object.keys(con.binds).length > 0) { - Object.keys(con.binds).forEach(key => { + Object.keys(con.binds).forEach((key) => { const thisSound = con.binds[key]; const context = thisSound.context; if (!context.playing) thisSound.currentTime = atTime; @@ -678,7 +907,12 @@ this.patchLinks(context.sourceNode, thisSound); }); } - if (sound.loop) this.loopParams({ NAME: con.name, START: con.loopParm[0], END: con.loopParm[1] }); + if (sound.loop) + this.loopParams({ + NAME: con.name, + START: con.loopParm[0], + END: con.loopParm[1], + }); } this.startHats({ name: con.name, type: "starts" }); } catch { @@ -693,11 +927,13 @@ const lastTime = sound.currentTime; ctx.stop(); sound.currentTime = lastTime; - for (let i = 0; i < sound.overlays.length; i++) sound.overlays[i].stop(); + for (let i = 0; i < sound.overlays.length; i++) + sound.overlays[i].stop(); this.startHats({ name: sound.name, type: "stops" }); } else if (type === "pause") { ctx.pause(); - for (let i = 0; i < sound.overlays.length; i++) sound.overlays[i].pause(); + for (let i = 0; i < sound.overlays.length; i++) + sound.overlays[i].pause(); this.startHats({ name: sound.name, type: "stops" }); } else { this.startHats({ name: sound.name, type: "starts" }); @@ -715,40 +951,65 @@ return new Promise((resolve) => { this.deleteSound(args); if (!args.URL) return resolve(); - const engine = new Pizzicato.Sound({ - source: "file", options: { path: args.URL, attack: 0 } - }, () => { - try { - engine.sourceNode = engine.getSourceNode(); - const bank = soundBank[args.NAME] = this.generateData(args.NAME, args.URL, engine, false); - engine.on("stop", () => { - bank.currentTime = engine.loop && bank.loopParm[1] ? bank.loopParm[1] : engine.sourceNode.buffer.duration; - }); - resolve(); - } catch { - alert("Tune Shark V3 Cant Import this Sound, File may be Corrupted or Non-Existent"); - resolve(); + const engine = new Pizzicato.Sound( + { + source: "file", + options: { path: args.URL, attack: 0 }, + }, + () => { + try { + engine.sourceNode = engine.getSourceNode(); + const bank = (soundBank[args.NAME] = this.generateData( + args.NAME, + args.URL, + engine, + false + )); + engine.on("stop", () => { + bank.currentTime = + engine.loop && bank.loopParm[1] + ? bank.loopParm[1] + : engine.sourceNode.buffer.duration; + }); + resolve(); + } catch { + alert( + "Tune Shark V3 Cant Import this Sound, File may be Corrupted or Non-Existent" + ); + resolve(); + } } - }); + ); }); } importMenu(args, util) { const name = Cast.toString(args.SOUND); const target = util.target.sprite; - const sound = target.sounds.find((i) => { return i.name === name }); + const sound = target.sounds.find((i) => { + return i.name === name; + }); if (sound) { this.deleteSound(args); const sourceURL = `/${target.name.replaceAll("/", "")}/${sound.name.replaceAll("/", "")}.${sound.dataFormat}`; const buffer = target.soundBank.soundPlayers[sound.soundId].buffer; const engine = new Pizzicato.Sound({ - source: "buffer", options: { buffer, attack: 0 } + source: "buffer", + options: { buffer, attack: 0 }, }); // this part of the Library was modified to work like this engine.sourceNode = engine.getSourceNode(); - const bank = soundBank[args.NAME] = this.generateData(args.NAME, sourceURL, engine, true); + const bank = (soundBank[args.NAME] = this.generateData( + args.NAME, + sourceURL, + engine, + true + )); engine.on("stop", () => { - bank.currentTime = engine.loop && bank.loopParm[1] ? bank.loopParm[1] : engine.sourceNode.buffer.duration; + bank.currentTime = + engine.loop && bank.loopParm[1] + ? bank.loopParm[1] + : engine.sourceNode.buffer.duration; }); } } @@ -765,7 +1026,9 @@ reader.readAsDataURL(audioBlob); }); await this.importURL({ NAME: args.NAME2, URL: audioDataURL }, util); - } catch (e) { console.error(e) } + } catch (e) { + console.error(e); + } } bindSound(args) { @@ -773,13 +1036,18 @@ const sound2 = soundBank[args.NAME2]; if (sound1 === undefined || sound2 === undefined) return; const shouldBind = args.TYPE === "bind"; - sound1.isBind = shouldBind; sound2.isBind = shouldBind; + sound1.isBind = shouldBind; + sound2.isBind = shouldBind; if (shouldBind) { - if (sound1.binds[sound2.name]) this.typeOverlay(sound1.binds[sound2.name], "stop"); - if (sound2.binds[sound1.name]) this.typeOverlay(sound2.binds[sound1.name], "stop"); - sound1.binds[sound2.name] = sound2; sound2.binds[sound1.name] = sound1; + if (sound1.binds[sound2.name]) + this.typeOverlay(sound1.binds[sound2.name], "stop"); + if (sound2.binds[sound1.name]) + this.typeOverlay(sound2.binds[sound1.name], "stop"); + sound1.binds[sound2.name] = sound2; + sound2.binds[sound1.name] = sound1; } else { - delete sound1.binds[sound2.name]; delete sound2.binds[sound1.name]; + delete sound1.binds[sound2.name]; + delete sound2.binds[sound1.name]; } } @@ -802,7 +1070,8 @@ this.play(sound.context, Cast.toNumber(args.TIME), sound); util.yield(); } else if (util.stackFrame.awaitingSound) { - if (sound.currentTime >= Cast.toNumber(args.MAX)) this.typeOverlay(sound, "stop"); + if (sound.currentTime >= Cast.toNumber(args.MAX)) + this.typeOverlay(sound, "stop"); else util.yield(); } } @@ -820,13 +1089,27 @@ } ctrlSounds(args) { - if (args.CONTROL === "start") Object.keys(soundBank).forEach(key => { this.play(soundBank[key].context, 0, soundBank[key]) }); - else if (args.CONTROL === "stop") Object.keys(soundBank).forEach(key => { soundBank[key].context.stop() }); - else if (args.CONTROL === "pause") Object.keys(soundBank).forEach(key => { this.typeOverlay(soundBank[key], "pause") }); - else Object.keys(soundBank).forEach(key => { this.typeOverlay(soundBank[key], "unpause") }); + if (args.CONTROL === "start") + Object.keys(soundBank).forEach((key) => { + this.play(soundBank[key].context, 0, soundBank[key]); + }); + else if (args.CONTROL === "stop") + Object.keys(soundBank).forEach((key) => { + soundBank[key].context.stop(); + }); + else if (args.CONTROL === "pause") + Object.keys(soundBank).forEach((key) => { + this.typeOverlay(soundBank[key], "pause"); + }); + else + Object.keys(soundBank).forEach((key) => { + this.typeOverlay(soundBank[key], "unpause"); + }); } - enableControl(args) { settings.flagCtrl = args.ON_OFF === "on" } + enableControl(args) { + settings.flagCtrl = args.ON_OFF === "on"; + } toggleOverlap(args) { const sound = soundBank[args.NAME]; @@ -857,9 +1140,10 @@ sound._cache = { loudness: {}, tone: {} }; const node = sound.context.sourceNode; const reverseBuffer = (buffer) => { - for (let i = 0; i < buffer.numberOfChannels; i++) buffer.getChannelData(i).reverse(); + for (let i = 0; i < buffer.numberOfChannels; i++) + buffer.getChannelData(i).reverse(); return buffer; - } + }; const bufferSource = node.context.createBufferSource(); bufferSource.buffer = reverseBuffer(node.buffer); bufferSource.connect(node.context.destination); @@ -886,10 +1170,12 @@ } deleteAllSounds() { - for (let name in soundBank) this.deleteSound({ NAME: name }) + for (let name in soundBank) this.deleteSound({ NAME: name }); } - allSounds() { return JSON.stringify(Object.keys(soundBank)) } + allSounds() { + return JSON.stringify(Object.keys(soundBank)); + } allPlaySounds() { const players = []; @@ -908,14 +1194,22 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return false; switch (args.CONTROL) { - case "exists": return sound.loaded; - case "playing": return sound.context.playing; - case "paused": return sound.context.paused; - case "looped": return sound.context.loop; - case "overlaped": return sound.overlap; - case "reversed": return sound.reversed; - case "binded": return sound.isBind; - default: return false; + case "exists": + return sound.loaded; + case "playing": + return sound.context.playing; + case "paused": + return sound.context.paused; + case "looped": + return sound.context.loop; + case "overlaped": + return sound.overlap; + case "reversed": + return sound.reversed; + case "binded": + return sound.isBind; + default: + return false; } } @@ -924,21 +1218,41 @@ if (sound === undefined) return 0; const src = sound.context.sourceNode; switch (args.PROP) { - case "length": return src.buffer.duration; - case "current time": return sound.currentTime; - case "estimated bpm": return this.getBPM(src.buffer.getChannelData(0), src.buffer.sampleRate); - case "channels": return src.buffer.numberOfChannels; - case "source": return sound.src; - case "binds": return JSON.stringify(Object.keys(sound.binds)); - case "volume": return sound.vol; - case "pitch": return Math.round((sound.pitch - 1) * 100); - case "detune": return sound.detune / 10; - case "speed": return sound.speed * 100; - case "gain": return sound.gain * 100; - case "pan": return sound.effects[args.PROP.toUpperCase()]?.options.pan * 100 || 0; - case "distortion": return sound.effects[args.PROP.toUpperCase()]?.options.gain * 100 || 0; - case "attack": return sound.context.attack * 100; - case "release": return sound.context.release * 100; + case "length": + return src.buffer.duration; + case "current time": + return sound.currentTime; + case "estimated bpm": + return this.getBPM( + src.buffer.getChannelData(0), + src.buffer.sampleRate + ); + case "channels": + return src.buffer.numberOfChannels; + case "source": + return sound.src; + case "binds": + return JSON.stringify(Object.keys(sound.binds)); + case "volume": + return sound.vol; + case "pitch": + return Math.round((sound.pitch - 1) * 100); + case "detune": + return sound.detune / 10; + case "speed": + return sound.speed * 100; + case "gain": + return sound.gain * 100; + case "pan": + return sound.effects[args.PROP.toUpperCase()]?.options.pan * 100 || 0; + case "distortion": + return ( + sound.effects[args.PROP.toUpperCase()]?.options.gain * 100 || 0 + ); + case "attack": + return sound.context.attack * 100; + case "release": + return sound.context.release * 100; default: { const effect = sound.effects[args.PROP.toUpperCase()]; if (effect === undefined) return ""; @@ -955,10 +1269,19 @@ const audioCtx = sound.context.sourceNode; const buffer = audioCtx.buffer; const duration = buffer.duration; - if (time < 0 || time > duration || chan < 0 || chan > buffer.numberOfChannels - 1) return 0; + if ( + time < 0 || + time > duration || + chan < 0 || + chan > buffer.numberOfChannels - 1 + ) + return 0; let value = 0; - if (args.TYPE !== "raw noise" && sound._cache[args.TYPE][`${time}${chan}`] !== undefined) + if ( + args.TYPE !== "raw noise" && + sound._cache[args.TYPE][`${time}${chan}`] !== undefined + ) value = sound._cache[args.TYPE][`${time}${chan}`]; else { const sampleRate = buffer.sampleRate; @@ -966,7 +1289,10 @@ const sampleIndex = Math.floor(sampleRate * time); const windowSize = sampleRate * 0.1; const startSample = Math.max(0, sampleIndex - windowSize / 2); - const endSample = Math.min(channelData.length, sampleIndex + windowSize / 2); + const endSample = Math.min( + channelData.length, + sampleIndex + windowSize / 2 + ); // no need to cache raw noise, no work is done if (args.TYPE === "raw noise") value = channelData[endSample]; @@ -997,12 +1323,15 @@ sound._cache["tone"][`${time}${chan}`] = value; return value; } else if (args.TYPE === "loudness") { - for (let i = startSample; i < endSample; i++) value += channelData[i] * channelData[i]; + for (let i = startSample; i < endSample; i++) + value += channelData[i] * channelData[i]; const rms = Math.sqrt(value / (endSample - startSample)); const dB = 20 * Math.log10(rms); value = Math.min(Math.max((dB + 50) / 50, 0), 1) * 100; sound._cache["loudness"][`${time}${chan}`] = value; - } else { return "" } + } else { + return ""; + } } return isNaN(value) ? 0 : value * sound.gain; } @@ -1026,10 +1355,14 @@ else if (effect === "attack") sound.context.attack = 0; else if (effect === "release") sound.context.release = 0; else if (effect === "all effects") { - sound.pitch = 1; sound.detune = 0; sound.speed = 1; - sound.gain = 1; sound.context.attack = 0; sound.context.release = 0; + sound.pitch = 1; + sound.detune = 0; + sound.speed = 1; + sound.gain = 1; + sound.context.attack = 0; + sound.context.release = 0; const effects = sound.effects; - Object.values(effects).forEach(e => ctx.removeEffect(e)); + Object.values(effects).forEach((e) => ctx.removeEffect(e)); sound.effects = {}; } if (sound.effects[name] !== undefined) { @@ -1049,10 +1382,14 @@ else if (args.TYPE === "detune") sound.detune = value * 1000; else if (args.TYPE === "speed") sound.speed = Math.max(0, value); else if (args.TYPE === "gain") sound.gain = value; - else if (args.TYPE === "attack") sound.context.attack = Math.max(0, value); - else if (args.TYPE === "release") sound.context.release = Math.max(0, value); + else if (args.TYPE === "attack") + sound.context.attack = Math.max(0, value); + else if (args.TYPE === "release") + sound.context.release = Math.max(0, value); else if (args.TYPE === "pan") { - const pan = new Pizzicato.Effects.StereoPanner({ pan: Math.max(-1, Math.min(1, value)) }); + const pan = new Pizzicato.Effects.StereoPanner({ + pan: Math.max(-1, Math.min(1, value)), + }); return this.updateEffect(pan, sound, "PAN", args); } else if (args.TYPE === "distortion") { const distort = new Pizzicato.Effects.Distortion({ gain: value }); @@ -1066,7 +1403,8 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; const reverb = new Pizzicato.Effects.Reverb({ - time: Cast.toNumber(args.TIME) / 10, decay: Cast.toNumber(args.DECAY) / 10, + time: Cast.toNumber(args.TIME) / 10, + decay: Cast.toNumber(args.DECAY) / 10, mix: Cast.toNumber(args.MIX) / 100, }); this.updateEffect(reverb, sound, "REVERB", args); @@ -1077,7 +1415,8 @@ if (sound === undefined) return; const delay = new Pizzicato.Effects.Delay({ time: Math.min(1, Math.max(0, Cast.toNumber(args.TIME) / 100)), - decay: Cast.toNumber(args.FEED) / 100, mix: Cast.toNumber(args.MIX) / 100, + decay: Cast.toNumber(args.FEED) / 100, + mix: Cast.toNumber(args.MIX) / 100, }); this.updateEffect(delay, sound, "DELAY", args); } @@ -1090,7 +1429,7 @@ midLowGain: Math.min(1, Math.max(0, Cast.toNumber(args.MED1) / 100)), midHighGain: Math.min(1, Math.max(0, Cast.toNumber(args.MED2) / 100)), highGain: Math.min(1, Math.max(0, Cast.toNumber(args.HIGH) / 100)), - mix: Cast.toNumber(args.MIX) / 100 + mix: Cast.toNumber(args.MIX) / 100, }); this.updateEffect(fuzz, sound, "FUZZ", args); } @@ -1103,7 +1442,7 @@ */ const bitcrush = new Pizzicato.Effects.Bitcrusher({ bits: Math.max(10, Cast.toNumber(args.BITS)) / 10, - frequency: Math.max(30000, Cast.toNumber(args.FREQ)) + frequency: Math.max(30000, Cast.toNumber(args.FREQ)), }); this.updateEffect(bitcrush, sound, "BITCRUSH", args); } @@ -1114,7 +1453,7 @@ const distort = new Pizzicato.Effects.Tremolo({ speed: Cast.toNumber(args.SPEED) / 5, depth: Cast.toNumber(args.DEPTH) / 100, - mix: Cast.toNumber(args.MIX) / 100 + mix: Cast.toNumber(args.MIX) / 100, }); this.updateEffect(distort, sound, "TREMOLO", args); } @@ -1122,7 +1461,10 @@ setPass(args) { const sound = soundBank[args.NAME]; if (sound === undefined) return; - const json = { frequency: Cast.toNumber(args.FREQ), peak:Cast.toNumber(args.PEAK) / 5 }; + const json = { + frequency: Cast.toNumber(args.FREQ), + peak: Cast.toNumber(args.PEAK) / 5, + }; if (args.TYPE === "highpass") { const highpass = new Pizzicato.Effects.HighPassFilter(json); this.updateEffect(highpass, sound, "HIGHPASS", args); @@ -1136,9 +1478,11 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; const flang = new Pizzicato.Effects.Flanger({ - time: Cast.toNumber(args.TIME) / 100, speed: Cast.toNumber(args.SPEED) / 100, - depth: Cast.toNumber(args.DEPTH) / 100, feedback: Cast.toNumber(args.FEED) / 100, - mix: Cast.toNumber(args.MIX) / 100 + time: Cast.toNumber(args.TIME) / 100, + speed: Cast.toNumber(args.SPEED) / 100, + depth: Cast.toNumber(args.DEPTH) / 100, + feedback: Cast.toNumber(args.FEED) / 100, + mix: Cast.toNumber(args.MIX) / 100, }); this.updateEffect(flang, sound, "FLANGER", args); } @@ -1148,8 +1492,10 @@ if (sound === undefined) return; const compress = new Pizzicato.Effects.Compressor({ threshold: Math.min(0, Math.max(-100, Cast.toNumber(args.THRESH) * -1)), - ratio: Cast.toNumber(args.RATIO) / 5, attack: Math.min(0, Math.max(1, Cast.toNumber(args.ATTACK) / 100)), - release: Math.min(0, Math.max(1, Cast.toNumber(args.RELEASE) / 100)), knee: Cast.toNumber(args.KNEE) / 2.5 + ratio: Cast.toNumber(args.RATIO) / 5, + attack: Math.min(0, Math.max(1, Cast.toNumber(args.ATTACK) / 100)), + release: Math.min(0, Math.max(1, Cast.toNumber(args.RELEASE) / 100)), + knee: Cast.toNumber(args.KNEE) / 2.5, }); this.updateEffect(compress, sound, "COMPRESSOR", args); } @@ -1158,11 +1504,11 @@ const sound = soundBank[args.NAME]; if (sound === undefined) return; const equalizer = new Pizzicato.Effects.ThreeBandEqualizer({ - cutoff_frequency_high: 12000 + (Cast.toNumber(args.CUT_HIGH) * 120), - cutoff_frequency_low: 12000 + (Cast.toNumber(args.CUT_LOW) * 120), + cutoff_frequency_high: 12000 + Cast.toNumber(args.CUT_HIGH) * 120, + cutoff_frequency_low: 12000 + Cast.toNumber(args.CUT_LOW) * 120, low_band_gain: Cast.toNumber(args.LOW) / 10, mid_band_gain: Cast.toNumber(args.MID) / 10, - high_band_gain: Cast.toNumber(args.HIGH) / 10 + high_band_gain: Cast.toNumber(args.HIGH) / 10, }); this.updateEffect(equalizer, sound, "EQUALIZER", args); } @@ -1172,9 +1518,14 @@ if (!Scratch.extensions.isPenguinMod) { if (settings.canSave) { const convertBank = JSON.parse(JSON.stringify(soundBank)); - Object.values(convertBank).forEach(item => delete item.context); - runtime.extensionStorage["SPtuneShark3"] = { bank: convertBank, settings }; - } else { runtime.extensionStorage["SPtuneShark3"] = undefined } + Object.values(convertBank).forEach((item) => delete item.context); + runtime.extensionStorage["SPtuneShark3"] = { + bank: convertBank, + settings, + }; + } else { + runtime.extensionStorage["SPtuneShark3"] = undefined; + } } } @@ -1182,11 +1533,13 @@ serialize() { if (settings.canSave) { const convertBank = JSON.parse(JSON.stringify(soundBank)); - Object.values(convertBank).forEach(item => delete item.context); - return { SPtuneShark3: { bank: convertBank, settings } } + Object.values(convertBank).forEach((item) => delete item.context); + return { SPtuneShark3: { bank: convertBank, settings } }; } } - deserialize(data) { this.loadStorage(data.SPtuneShark3) } + deserialize(data) { + this.loadStorage(data.SPtuneShark3); + } } Scratch.extensions.register(new SPtuneShark3()); From 916b785fe06ebc4789e456140dc148b45e5ff98a Mon Sep 17 00:00:00 2001 From: Cubester Date: Fri, 17 Jan 2025 19:42:42 -0500 Subject: [PATCH 32/40] Parity and Translation functions --- extensions/SharkPool/Tune-Shark-V3.js | 318 ++++++++++++++++---------- 1 file changed, 199 insertions(+), 119 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 431afcf0db..bc54fcff16 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -4,12 +4,12 @@ // By: SharkPool // License: MIT AND LGPL-3.0 -// Version V.3.4.22 +// Version V.3.4.23 (function (Scratch) { "use strict"; if (!Scratch.extensions.unsandboxed) - throw new Error("Tune Shark V3 must be run unsandboxed"); + throw new Error(Scratch.translate("Tune Shark V3 must be run unsandboxed")); const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDIuMTg1IiBoZWlnaHQ9IjEwMi4xODUiIHZpZXdCb3g9IjAgMCAxMDIuMTg1IDEwMi4xODUiPjxnIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTAgNTEuMDkzQzAgMjIuODc1IDIyLjg3NSAwIDUxLjA5MyAwczUxLjA5MyAyMi44NzUgNTEuMDkzIDUxLjA5My0yMi44NzUgNTEuMDkzLTUxLjA5MyA1MS4wOTNTMCA3OS4zMTEgMCA1MS4wOTMiIGZpbGw9IiM0MDQwNDAiLz48cGF0aCBkPSJNNC44NiA1MS4wOTNjMC0yNS41MzQgMjAuNy00Ni4yMzMgNDYuMjMzLTQ2LjIzMyAyNS41MzQgMCA0Ni4yMzMgMjAuNyA0Ni4yMzMgNDYuMjMzIDAgMjUuNTM0LTIwLjcgNDYuMjMzLTQ2LjIzMyA0Ni4yMzMtMjUuNTM0IDAtNDYuMjMzLTIwLjctNDYuMjMzLTQ2LjIzMyIgZmlsbD0iIzY2NiIvPjxwYXRoIGQ9Ik03Mi44MzcgODYuNjQzdi0uMDAzYy0xLjI1NCAyLjUzNi00LjY2OCAzLjkzNS04LjI2NCAzLjE5Ny00LjExOC0uODQ0LTYuOTE1LTQuMTctNi4yNDYtNy40MjguNjY4LTMuMjYgNC41NDgtNS4yMTYgOC42NjYtNC4zNzEgMS44NzUuMzg0IDMuNDc0IDEuMjg0IDQuNiAyLjQ1N2w2LjY4My0xNC4xNzhjLTEwLjU2Ni00LjEzNS0xOS43Ni01LjA5Ni0xOS43Ni01LjA5NmwtOC45ODcgMTkuMDYxYy0uOTY2IDIuOTI3LTQuNjM2IDQuNjIyLTguNTIgMy44MjYtNC4xMTctLjg0NC02LjkxNC00LjE3LTYuMjQ2LTcuNDMuNjY5LTMuMjU4IDQuNTQ4LTUuMjE0IDguNjY3LTQuMzcgMS45MS4zOTEgMy41MzYgMS4zMTcgNC42NjQgMi41MjJsMTIuMDM1LTI1LjUwN3MxMy41MzIuMjM2IDI2Ljk0NyA3LjExNHoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtMjguMzA5IDMwLjgzMSA0LjA0MyAyMy42ODQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIi8+PHBhdGggZD0iTTM0LjAzMyA1NS4yMTljMS4zOCAyLjYwNi0uNzcyIDYuMDQtNC44MDggNy42Ny00LjAzNyAxLjYzLTguNDI5LjgzNy05LjgxLTEuNzctMS4zOC0yLjYwNi43NzItNi4wNCA0LjgwOS03LjY3IDQuMDM2LTEuNjI5IDguNDI4LS44MzcgOS44MDkgMS43N20tNy45NS0yNy4wNjhzOS43MDUtMS43MDQgMTIuMzYzIDIuNzdjMi4zNzUgNCAuMDcxIDguNjk1LjIxMiAxMC4xMjguMTQgMS40MzMgMi4xNzUgMS4xMDkgMi4xNzUgMS4xMDlsLS4wMTQgMS42NzRzLTIuODY0LjY2OS0zLjQxMi0xLjMyYy0uNTQ3LTEuOTg4LS41Ni01Ljk3OC0yLjgyMy04LjIyNy0yLjI2NS0yLjI1LTcuNTM3LS43NTktNy41MzctLjc1OSIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik02MC40MzYgMzUuMzcxYy0uMzMzIDIuMTE4LTIuMzUzIDMuMzA5LTQuMTg5IDQuMDAyLTEuNjUuNzI2LTMuNTUgMS4wMDgtNS4yNzYuMzctMS42MzEtLjM4MS0zLjE4OC0xLjgwNy0yLjk5NC0zLjU5OC4xNTQtMi4wODUgMS44OTYtMy44MjIgMy44NDItNC40MDUgMS45NC0uNzk5IDQuMjUzLS43MyA2LjEwNS4yNi45MTMuNDIxIDIuMTg5LTE0LjE0MiAzLjAzNS0yMC41ODMuMS0uNjQyIDIuNTg0LS40NyAyLjUxMy4xNTEgMCAwLTEuODU0IDE1LjUyMi0zLjAzNiAyMy44MDMiIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4="; @@ -40,26 +40,26 @@ const ts3Data = Symbol("ts3Data"); const simpleEffects = [ - "pitch", - "detune", - "speed", - "pan", - "gain", - "distortion", - "attack", - "release", + { text: Scratch.translate("pitch"), value: "pitch" }, + { text: Scratch.translate("detune"), value: "detune" }, + { text: Scratch.translate("speed"), value: "speed" }, + { text: Scratch.translate("pan"), value: "pan" }, + { text: Scratch.translate("gain"), value: "gain" }, + { text: Scratch.translate("distortion"), value: "distortion" }, + { text: Scratch.translate("attack"), value: "attack" }, + { text: Scratch.translate("release"), value: "release" }, ]; const complexEffects = [ - "reverb", - "delay", - "tremolo", - "fuzz", - "bitcrush", - "highpass", - "lowpass", - "flanger", - "compressor", - "equalizer", + { text: Scratch.translate("reverb"), value: "reverb" }, + { text: Scratch.translate("delay"), value: "delay" }, + { text: Scratch.translate("tremolo"), value: "tremolo" }, + { text: Scratch.translate("fuzz"), value: "fuzz" }, + { text: Scratch.translate("bitcrush"), value: "bitcrush" }, + { text: Scratch.translate("highpass"), value: "highpass" }, + { text: Scratch.translate("lowpass"), value: "lowpass" }, + { text: Scratch.translate("flanger"), value: "flanger" }, + { text: Scratch.translate("compressor"), value: "compressor" }, + { text: Scratch.translate("equalizer"), value: "equalizer" }, ]; let deltaTime = 0, @@ -86,7 +86,9 @@ : runtime.getSpriteTargetByName(info[0]); if (target === undefined) { alert( - `Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}` + Scratch.translate( + `Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}` + ) ); continue; } @@ -96,7 +98,9 @@ }); if (scratchSound === undefined) { alert( - `Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}` + Scratch.translate( + `Tune Shark 3 -- Failed to load ${info[1]} from ${info[0]}` + ) ); continue; } @@ -185,7 +189,7 @@ getInfo() { return { id: "SPtuneShark3", - name: "Tune Shark V3", + name: Scratch.translate("Tune Shark V3"), color1: "#666666", menuIconURI, blockIconURI, @@ -193,7 +197,7 @@ { opcode: "importURL", blockType: Scratch.BlockType.COMMAND, - text: "import sound from URL [URL] named [NAME]", + text: Scratch.translate("import sound from URL [URL] named [NAME]"), blockIconURI: extraIcons.set, arguments: { URL: { @@ -202,85 +206,90 @@ }, NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, }, }, { opcode: "importMenu", blockType: Scratch.BlockType.COMMAND, - text: "import sound [SOUND] named [NAME]", + text: Scratch.translate("import sound [SOUND] named [NAME]"), blockIconURI: extraIcons.set, arguments: { SOUND: { type: Scratch.ArgumentType.SOUND }, NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, }, }, { opcode: "convertSound", blockType: Scratch.BlockType.COMMAND, - text: "convert sound [NAME1] from URL to URI and save to [NAME2]", + text: Scratch.translate( + "convert sound [NAME1] from URL to URI and save to [NAME2]" + ), blockIconURI: extraIcons.set, arguments: { NAME1: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, NAME2: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound2", + defaultValue: Scratch.translate("MySound2"), }, }, }, { opcode: "bindSound", blockType: Scratch.BlockType.COMMAND, - text: "[TYPE] sound [NAME2] and sound [NAME]", + text: Scratch.translate("[TYPE] sound [NAME2] and sound [NAME]"), blockIconURI: extraIcons.set, arguments: { TYPE: { type: Scratch.ArgumentType.STRING, menu: "bindMenu" }, NAME2: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound2", + defaultValue: Scratch.translate("MySound2"), }, }, }, { opcode: "save2Project", blockType: Scratch.BlockType.COMMAND, - text: "[SAVE] all sounds to project", + text: Scratch.translate("[SAVE] all sounds to project"), blockIconURI: extraIcons.set, arguments: { SAVE: { type: Scratch.ArgumentType.STRING, menu: "saveMenu" }, }, }, - { blockType: Scratch.BlockType.LABEL, text: "Audio Playback" }, + { + blockType: Scratch.BlockType.LABEL, + text: Scratch.translate("Audio Playback"), + }, { opcode: "startSound", blockType: Scratch.BlockType.COMMAND, - text: "start sound [NAME]", + text: Scratch.translate("start sound [NAME]"), arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, }, }, { opcode: "startSoundAt", blockType: Scratch.BlockType.COMMAND, - text: "start sound [NAME] at time [TIME]", + text: Scratch.translate("start sound [NAME] at time [TIME]"), arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 5 }, }, @@ -288,11 +297,13 @@ { opcode: "playAndStop", blockType: Scratch.BlockType.COMMAND, - text: "start sound [NAME] at time [TIME] and stop at [MAX] seconds", + text: Scratch.translate( + "start sound [NAME] at time [TIME] and stop at [MAX] seconds" + ), arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, MAX: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 }, @@ -301,22 +312,22 @@ { opcode: "stopSound", blockType: Scratch.BlockType.COMMAND, - text: "stop sound [NAME]", + text: Scratch.translate("stop sound [NAME]"), arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, }, }, { opcode: "pauseSound", blockType: Scratch.BlockType.COMMAND, - text: "[UN_PAUSE] sound [NAME]", + text: Scratch.translate("[UN_PAUSE] sound [NAME]"), arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, UN_PAUSE: { type: Scratch.ArgumentType.STRING, @@ -328,16 +339,21 @@ { opcode: "ctrlSounds", blockType: Scratch.BlockType.COMMAND, - text: "[CONTROL] all sounds", + text: Scratch.translate("[CONTROL] all sounds"), arguments: { CONTROL: { type: Scratch.ArgumentType.STRING, menu: "playMenu" }, }, }, - { blockType: Scratch.BlockType.LABEL, text: "Audio Operations" }, + { + blockType: Scratch.BlockType.LABEL, + text: Scratch.translate("Audio Operations"), + }, { opcode: "enableControl", blockType: Scratch.BlockType.COMMAND, - text: "toggle sound link to [GO] [STOP] [ON_OFF]", + text: Scratch.translate( + "toggle sound link to [GO] [STOP] [ON_OFF]" + ), blockIconURI: extraIcons.set, arguments: { GO: { @@ -354,12 +370,12 @@ { opcode: "toggleOverlap", blockType: Scratch.BlockType.COMMAND, - text: "toggle sound [NAME] overlapping [TYPE]", + text: Scratch.translate("toggle sound [NAME] overlapping [TYPE]"), blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" }, }, @@ -367,12 +383,12 @@ { opcode: "toggleReverse", blockType: Scratch.BlockType.COMMAND, - text: "toggle sound [NAME] reverse mode [TYPE]", + text: Scratch.translate("toggle sound [NAME] reverse mode [TYPE]"), blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" }, }, @@ -380,12 +396,12 @@ { opcode: "toggleLoop", blockType: Scratch.BlockType.COMMAND, - text: "toggle sound [NAME] looping [TYPE]", + text: Scratch.translate("toggle sound [NAME] looping [TYPE]"), blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "toggleMenu" }, }, @@ -393,12 +409,14 @@ { opcode: "loopParams", blockType: Scratch.BlockType.COMMAND, - text: "sound [NAME] loop start [START] end [END]", + text: Scratch.translate( + "sound [NAME] loop start [START] end [END]" + ), blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, START: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, END: { type: Scratch.ArgumentType.NUMBER, defaultValue: 2 }, @@ -408,44 +426,44 @@ { opcode: "deleteSound", blockType: Scratch.BlockType.COMMAND, - text: "delete sound [NAME]", + text: Scratch.translate("delete sound [NAME]"), blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, }, }, { opcode: "deleteAllSounds", blockType: Scratch.BlockType.COMMAND, - text: "delete all sounds", + text: Scratch.translate("delete all sounds"), blockIconURI: extraIcons.set, }, { opcode: "allSounds", blockType: Scratch.BlockType.REPORTER, - text: "all sounds", + text: Scratch.translate("all sounds"), blockIconURI: extraIcons.set, }, { opcode: "allPlaySounds", blockType: Scratch.BlockType.REPORTER, - text: "all playing sounds", + text: Scratch.translate("all playing sounds"), blockIconURI: extraIcons.set, }, "---", { opcode: "whenSound", blockType: Scratch.BlockType.HAT, - text: "when sound [NAME] [CONTROL]", + text: Scratch.translate("when sound [NAME] [CONTROL]"), isEdgeActivated: false, blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, CONTROL: { type: Scratch.ArgumentType.STRING, menu: "hatPlayer" }, }, @@ -453,12 +471,12 @@ { opcode: "soundCheck", blockType: Scratch.BlockType.BOOLEAN, - text: "sound [NAME] [CONTROL] ?", + text: Scratch.translate("sound [NAME] [CONTROL] ?"), blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, CONTROL: { type: Scratch.ArgumentType.STRING, @@ -469,12 +487,12 @@ { opcode: "soundProperty", blockType: Scratch.BlockType.REPORTER, - text: "[PROP] of sound [NAME]", + text: Scratch.translate("[PROP] of sound [NAME]"), blockIconURI: extraIcons.set, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, PROP: { type: Scratch.ArgumentType.STRING, menu: "soundProps" }, }, @@ -482,28 +500,33 @@ { opcode: "getLoudTime", blockType: Scratch.BlockType.REPORTER, - text: "[TYPE] of sound [NAME] at time [TIME] in channel [CHANNEL]", + text: Scratch.translate( + "[TYPE] of sound [NAME] at time [TIME] in channel [CHANNEL]" + ), blockIconURI: extraIcons.set, arguments: { TYPE: { type: Scratch.ArgumentType.STRING, menu: "loudProps" }, NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, CHANNEL: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, }, }, - { blockType: Scratch.BlockType.LABEL, text: "Audio Effects" }, + { + blockType: Scratch.BlockType.LABEL, + text: Scratch.translate("Audio Effects"), + }, { opcode: "setVol", blockType: Scratch.BlockType.COMMAND, - text: "set volume of sound [NAME] to [NUM]", + text: Scratch.translate("set volume of sound [NAME] to [NUM]"), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, NUM: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, }, @@ -511,13 +534,13 @@ { opcode: "resetEffect", blockType: Scratch.BlockType.COMMAND, - text: "reset [EFFECT] of sound [NAME]", + text: Scratch.translate("reset [EFFECT] of sound [NAME]"), blockIconURI: extraIcons.nob, arguments: { EFFECT: { type: Scratch.ArgumentType.STRING, menu: "effectMenu" }, NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, }, }, @@ -525,12 +548,12 @@ { opcode: "setThingNew", blockType: Scratch.BlockType.COMMAND, - text: "set [TYPE] of sound [NAME] to [VALUE]", + text: Scratch.translate("set [TYPE] of sound [NAME] to [VALUE]"), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TYPE: { type: Scratch.ArgumentType.STRING, @@ -542,12 +565,14 @@ { opcode: "setReverb", blockType: Scratch.BlockType.COMMAND, - text: "set reverb of sound [NAME] to time [TIME] decay [DECAY] mix [MIX]", + text: Scratch.translate( + "set reverb of sound [NAME] to time [TIME] decay [DECAY] mix [MIX]" + ), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, DECAY: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, @@ -557,12 +582,14 @@ { opcode: "setDelay", blockType: Scratch.BlockType.COMMAND, - text: "set delay of sound [NAME] to time [TIME] feedback [FEED] mix [MIX]", + text: Scratch.translate( + "set delay of sound [NAME] to time [TIME] feedback [FEED] mix [MIX]" + ), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, FEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, @@ -572,12 +599,14 @@ { opcode: "setTremolo", blockType: Scratch.BlockType.COMMAND, - text: "set tremolo of sound [NAME] to speed [SPEED] depth [DEPTH] mix [MIX]", + text: Scratch.translate( + "set tremolo of sound [NAME] to speed [SPEED] depth [DEPTH] mix [MIX]" + ), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, SPEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 35 }, DEPTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 80 }, @@ -588,12 +617,14 @@ { opcode: "setFuzz", blockType: Scratch.BlockType.COMMAND, - text: "set fuzz of sound [NAME] to low [LOW] med-low [MED1] med-high [MED2] high [HIGH] mix [MIX]", + text: Scratch.translate( + "set fuzz of sound [NAME] to low [LOW] med-low [MED1] med-high [MED2] high [HIGH] mix [MIX]" + ), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, LOW: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60 }, MED1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 80 }, @@ -605,12 +636,14 @@ { opcode: "setBitcrush", blockType: Scratch.BlockType.COMMAND, - text: "set bitcrush of sound [NAME] bits [BITS] freq [FREQ]", + text: Scratch.translate( + "set bitcrush of sound [NAME] bits [BITS] freq [FREQ]" + ), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, BITS: { type: Scratch.ArgumentType.NUMBER, defaultValue: 65 }, FREQ: { type: Scratch.ArgumentType.NUMBER, defaultValue: 60000 }, @@ -619,12 +652,14 @@ { opcode: "setPass", blockType: Scratch.BlockType.COMMAND, - text: "set [TYPE] of sound [NAME] to frequency [FREQ] peak [PEAK]", + text: Scratch.translate( + "set [TYPE] of sound [NAME] to frequency [FREQ] peak [PEAK]" + ), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TYPE: { type: Scratch.ArgumentType.STRING, menu: "typePass" }, FREQ: { type: Scratch.ArgumentType.NUMBER, defaultValue: 400 }, @@ -634,12 +669,14 @@ { opcode: "setFlanger", blockType: Scratch.BlockType.COMMAND, - text: "set flanger of sound [NAME] to time [TIME] speed [SPEED] depth [DEPTH] feed [FEED] mix [MIX]", + text: Scratch.translate( + "set flanger of sound [NAME] to time [TIME] speed [SPEED] depth [DEPTH] feed [FEED] mix [MIX]" + ), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, TIME: { type: Scratch.ArgumentType.NUMBER, defaultValue: 45 }, SPEED: { type: Scratch.ArgumentType.NUMBER, defaultValue: 20 }, @@ -651,12 +688,14 @@ { opcode: "setCompress", blockType: Scratch.BlockType.COMMAND, - text: "set compressor of sound [NAME] to threshold [THRESH] knee [KNEE] attack [ATTACK] release [RELEASE] ratio [RATIO]", + text: Scratch.translate( + "set compressor of sound [NAME] to threshold [THRESH] knee [KNEE] attack [ATTACK] release [RELEASE] ratio [RATIO]" + ), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, THRESH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 15 }, KNEE: { type: Scratch.ArgumentType.NUMBER, defaultValue: 50 }, @@ -668,12 +707,14 @@ { opcode: "setEqualize", blockType: Scratch.BlockType.COMMAND, - text: "set equalizer of sound [NAME] to gain low [LOW] mid [MID] high [HIGH] cutoff low [CUT_LOW] cutoff high [CUT_HIGH]", + text: Scratch.translate( + "set equalizer of sound [NAME] to gain low [LOW] mid [MID] high [HIGH] cutoff low [CUT_LOW] cutoff high [CUT_HIGH]" + ), blockIconURI: extraIcons.nob, arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: "MySound", + defaultValue: Scratch.translate("MySound"), }, LOW: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, MID: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, @@ -684,42 +725,77 @@ }, ], menus: { - saveMenu: ["save", "dont save"], - un_pauseMenu: ["pause", "unpause"], - playMenu: ["start", "stop", "pause", "unpause"], - hatPlayer: ["starts", "stops"], - toggleMenu: ["on", "off"], - bindMenu: ["bind", "unBind"], - loudProps: ["loudness", "raw noise", "tone"], - typePass: ["highpass", "lowpass"], + saveMenu: [ + { text: Scratch.translate("save"), value: "save" }, + { text: Scratch.translate("dont save"), value: "dont save" }, + ], + un_pauseMenu: [ + { text: Scratch.translate("pause"), value: "pause" }, + { text: Scratch.translate("unpause"), value: "unpause" }, + ], + playMenu: [ + { text: Scratch.translate("start"), value: "start" }, + { text: Scratch.translate("stop"), value: "stop" }, + { text: Scratch.translate("pause"), value: "pause" }, + { text: Scratch.translate("unpause"), value: "unpause" }, + ], + hatPlayer: [ + { text: Scratch.translate("starts"), value: "starts" }, + { text: Scratch.translate("stops"), value: "stops" }, + ], + toggleMenu: [ + { text: Scratch.translate("on"), value: "on" }, + { text: Scratch.translate("off"), value: "off" }, + ], + bindMenu: [ + { text: Scratch.translate("bind"), value: "bind" }, + { text: Scratch.translate("unBind"), value: "unBind" }, + ], + loudProps: [ + { text: Scratch.translate("loudness"), value: "loudness" }, + { text: Scratch.translate("raw noise"), value: "raw noise" }, + { text: Scratch.translate("tone"), value: "tone" }, + ], + typePass: [ + { text: Scratch.translate("highpass"), value: "highpass" }, + { text: Scratch.translate("lowpass"), value: "lowpass" }, + ], singleEffectNew: { acceptReporters: true, items: simpleEffects }, soundProps: { acceptReporters: true, items: [ - "length", - "current time", - "source", - "estimated bpm", - "channels", - "binds", - "volume", + { text: Scratch.translate("length"), value: "length" }, + { + text: Scratch.translate("current time"), + value: "current time", + }, + { text: Scratch.translate("source"), value: "source" }, + { + text: Scratch.translate("estimated bpm"), + value: "estimated bpm", + }, + { text: Scratch.translate("channels"), value: "channels" }, + { text: Scratch.translate("binds"), value: "binds" }, + { text: Scratch.translate("volume"), value: "volume" }, ].concat(simpleEffects, complexEffects), }, soundBools: { acceptReporters: true, items: [ - "exists", - "playing", - "paused", - "looped", - "overlaped", - "reversed", - "binded", + { text: Scratch.translate("exists"), value: "exists" }, + { text: Scratch.translate("playing"), value: "playing" }, + { text: Scratch.translate("paused"), value: "paused" }, + { text: Scratch.translate("looped"), value: "looped" }, + { text: Scratch.translate("overlaped"), value: "overlaped" }, + { text: Scratch.translate("reversed"), value: "reversed" }, + { text: Scratch.translate("binded"), value: "binded" }, ], }, effectMenu: { acceptReporters: true, - items: ["all effects"].concat(simpleEffects, complexEffects), + items: [ + { text: Scratch.translate("all effects"), value: "all effects" }, + ].concat(simpleEffects, complexEffects), }, }, }; @@ -916,7 +992,9 @@ } this.startHats({ name: con.name, type: "starts" }); } catch { - console.warn("Audio has not Loaded Yet, Ignore Next Error"); + console.warn( + Scratch.translate("Audio has not loaded yet, ignore next error.") + ); sound.stop(); // Reset } } @@ -974,7 +1052,9 @@ resolve(); } catch { alert( - "Tune Shark V3 Cant Import this Sound, File may be Corrupted or Non-Existent" + Scratch.translate( + "Tune Shark V3 can't import this sound, file may be corrupted or non-existent." + ) ); resolve(); } From b8df9bad80d718a39978674a822026c32be37900 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Fri, 17 Jan 2025 20:55:53 -0800 Subject: [PATCH 33/40] Update extensions.json --- extensions/extensions.json | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/extensions.json b/extensions/extensions.json index 1afb46a935..c908b913b5 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -46,6 +46,7 @@ "XeroName/Deltatime", "ar", "encoding", + "SharkPool/Tune-Shark-V3", "Lily/SoundExpanded", "Lily/TempVariables2", "Lily/MoreTimers", From 02fa269a75ecd51fe625ada075abb34cfd4f1d74 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Fri, 17 Jan 2025 20:58:57 -0800 Subject: [PATCH 34/40] well exuseeeeeeeeeeee me for not adding punctuation --- extensions/SharkPool/Tune-Shark-V3.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index bc54fcff16..467f2d55c9 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -1,6 +1,6 @@ // Name: Tune Shark V3 // ID: SPtuneShark3 -// Description: Advanced Audio Engine, giving Complex Sound Control +// Description: Advanced Audio Engine, giving Complex Sound Control. // By: SharkPool // License: MIT AND LGPL-3.0 From 55ae40b835de6c5e67d993c4d043a1e1dd9f4273 Mon Sep 17 00:00:00 2001 From: Cubester Date: Sat, 18 Jan 2025 17:02:08 -0500 Subject: [PATCH 35/40] Parity --- extensions/SharkPool/Tune-Shark-V3.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 467f2d55c9..72b2ef12cc 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -1,6 +1,6 @@ // Name: Tune Shark V3 // ID: SPtuneShark3 -// Description: Advanced Audio Engine, giving Complex Sound Control. +// Description: An advanced audio engine, providing complex sound control. // By: SharkPool // License: MIT AND LGPL-3.0 From 6cf7ee856d6912668e7b1e2e9ba304c1b5d5da9d Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 19 Jan 2025 01:12:17 -0800 Subject: [PATCH 36/40] documentation draft --- docs/SharkPool/Tune-Shark-V3.md | 255 ++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 docs/SharkPool/Tune-Shark-V3.md diff --git a/docs/SharkPool/Tune-Shark-V3.md b/docs/SharkPool/Tune-Shark-V3.md new file mode 100644 index 0000000000..6bf720065f --- /dev/null +++ b/docs/SharkPool/Tune-Shark-V3.md @@ -0,0 +1,255 @@ +# Tune Shark V3 +Tune Shark V3 is a powerful audio engine that is built to give you full control over your sounds. It allows you to apply numerous audio effects, precisely measure sound outputs, and more! +This is a remaster of the now deprecated Tune Shark extension by SharkPool. + +## General Setup + +### Note: All Tune Shark sounds are globally accessible in your project. This means all sprites will be able to access a loaded sound. + +```scratch +⚙️ import sound from URL [https://...] named [MySound] ::#666666 +``` + +You can import "Tune Shark" sounds via URL/URI using this block. It is important to note that URLs *must* be a **direct** link to an audio file + +```scratch +⚙️ import sound (Meow v) named [MySound] ::#666666 +``` + +Alternatively, you can import "Tune Shark" sounds from pre-existing sounds from the "Sounds" tab in the editor. + +```scratch +⚙️ convert sound [MySound] from URL to URI and save to [MySound2] ::#666666 +``` + +This block will convert the inputted sound's source from a URL to a data:URI. It will save this new sound as a new "Tune Shark" sound. + +```scratch +⚙️ [bind v] sound [MySound] and sound [MySound2] ::#666666 +``` + +This block allows you to bind or unbind the first inputted sound to the second. This means when you play "MySound2", "MySound" will play as well. +This **does not** go the same way in reverse. + +```scratch +⚙️ [save v] all sounds to project ::#666666 +``` + +This block saves all currently loaded Tune Shark sounds into the project so that they may be automatically loaded when the project opens. +You can stop this by using the "dont save" option in the block menu. + +## Audio Playback + +```scratch +🎵 start sound [MySound] ::#666666 +``` +Similar to Scratch, this will start a sound from the beginning. + +```scratch +🎵 start sound [MySound] at time (5) ::#666666 +``` + +You can also start sounds at certain points in the track using this block. This is measured in seconds. + +```scratch +🎵 start sound [MySound] at time (0) and stop at (2) ::#666666 +``` + +Similar to the above block, this block will start a sound at a certain point, then **waits** until the sound reaches the stopping point. + +```scratch +🎵 stop sound [MySound] ::#666666 +``` + +This block will stop the inputted sound. + +```scratch +🎵 [pause v] sound [MySound] ::#666666 +``` + +This block will pause/unpause the inputted sound. + +```scratch +🎵 [start v] all sounds ::#666666 +``` + +This block controls all loaded sounds. You can: +- start all sounds +- stop all sounds +- pause all sounds +- unpause all sounds + +## Operations + +```scratch +⚙️ toggle sound link to @greenFlag @stopSign [on v] ::#666666 +``` + +Toggling this operator on will cause Tune Shark sounds to stop when the green flag or stop sign is clicked. + +```scratch +⚙️ toggle sound [MySound] overlapping [on v] ::#666666 +``` + +Toggling this operator on will allow multiple instances of a sound to play at once. +Normally, you can only play one instance of a sound at a time. + +```scratch +⚙️ toggle sound [MySound] reverse mode [on v] ::#666666 +``` + +Toggling this operator on will make the inputted ound play in reverse. Toggling it off will return it back to normal. + +```scratch +⚙️ toggle sound [MySound] looping [on v] ::#666666 +``` + +Toggling this operator on will allow the inputted sound to loop. + +```scratch +⚙️ sound [MySound] loop start (0) end (2) ::#666666 +``` + +You can mess around with the loop starting point and ending point with this block. + +```scratch +⚙️ delete sound [MySound] ::#666666 +``` + +This block will delete the inputted sound. + +```scratch +⚙️ delete all sounds ::#666666 +``` + +This block will delete all loaded sounds. + +```scratch +(⚙️ all sounds ::#666666) +``` + +This block will return an array of the names of all loaded Tune Shark sounds. + +```scratch +(⚙️ all playing sounds ::#666666) +``` + +Similarly, this block returns an array of the names of all loaded Tune Shark sounds that are currently playing. + +```scratch +⚙️ when sound [MySound] [starts v] ::#666666 hat +``` + +This event block runs whenever the inputted sound starts or ends. + +```scratch +<⚙️ sound [MySound] (exists v)? ::#666666> +``` + +Returns various information of a sound: +Options | What they Check for +--- | --- +exists | if the sound exists +playing | if the sound is playing +paused | if the sound is paused +looped | if the sound is looping +overlaped | if the sound allows instancing +reversed | if the sound is reversed +binded | if the sound is binded to another + +```scratch +(⚙️ (length v) of sound [MySound] ::#666666) +``` + +Returns various information of a sound: +Options | What they Do +--- | --- +length | sound length (in seconds) +current time | current position in a sound +source | source URL/URI of a sound +estimated bpm | estimated beats-per-minute +channels | the number of [channels](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Audio_concepts) in a sound +binds | an array of sound names binded to this sound +*...various audio effects* | returns the inputted parameters for the effect + +```scratch +(⚙️ [loudness v] of sound [MySound] at time (0) in channel (1) ::#666666) +``` + +You can read sound outputs using this block. + +If you select **"loudness"**, it returns a normalized volume of the outputted noise at a specific point in the sound. + +Similarly, **"raw noise"** returns the outputted volume, but its *not normalized* + +Selecting **"tone"** will return the outputted pitch of a sound at a specific point. + + +The channel input selects which [sound channel](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Audio_concepts) to extract the output data from. +Typically, if there are 2 channels, channel 1 is "Left Ear" and channel 2 is "Right Ear". + +## Audio Effects + +```scratch +🎛️ set volume of sound [MySound] to (100) ::#666666 +``` + +This block simply sets the volume of the inputted sound. + +```scratch +🎛️ reset (all effects v) of sound [MySound] ::#666666 +``` + +This block resets the values of each audio effect in a sound to the default (not including volume). + +```scratch +🎛️ set (pitch v) of sound [MySound] to (0) ::#666666 +``` + +Sets the effect of a selected sound to a inputted value. +Effect List | What they Do +--- | --- +pitch | changes the speed and tone of the sound +detune | changes the speed and semitone of the sound +speed | changes the speed of the sound without affecting pitch +pan | shifts sound to the left (negative) or right (positive) ear +gain | boosts the sounds volume beyond 100 +distortion | distorts/crushes sound +attack | fade-in time when sound starts +release | fade-out time when sound stops + +```scratch +🎛️ set reverb of sound [MySound] to time (100) decay (100) mix (50) ::#666666 +``` + +```scratch +🎛️ set delay of sound [MySound] to time (50) feedback (60) mix (50) ::#666666 +``` + +```scratch +🎛️ set tremolo of sound [MySound] to speed (35) depth (80) mix (100) ::#666666 +``` + +```scratch +🎛️ set fuzz of sound [MySound] to low (60) med-low (50) med-high (80) high (60) mix (50) ::#666666 +``` + +```scratch +🎛️ set bitcrush of sound [MySound] to bits (65) freq (60000) ::#666666 +``` + +```scratch +🎛️ set [highpass v] of sound [MySound] to frequency (400) peak (10) ::#666666 +``` + +```scratch +🎛️ set flanger of sound [MySound] to time (45) speed (20) depth (10) feed (10) mix (50) ::#666666 +``` + +```scratch +🎛️ set compressor of sound [MySound] to threshold (15) knee (50) attack (50) release (50) ratio (50) ::#666666 +``` + +```scratch +🎛️ set equalizer of sound [MySound] to gain low (100) med (100) high (100) cutoff low (-50) cutoff high (50) ::#666666 +``` From 58d5a3f6f5e26b17b6ad149512972946b19a186c Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:42:46 -0800 Subject: [PATCH 37/40] finish documentation --- docs/SharkPool/Tune-Shark-V3.md | 81 +++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/docs/SharkPool/Tune-Shark-V3.md b/docs/SharkPool/Tune-Shark-V3.md index 6bf720065f..0e9a307359 100644 --- a/docs/SharkPool/Tune-Shark-V3.md +++ b/docs/SharkPool/Tune-Shark-V3.md @@ -222,34 +222,115 @@ release | fade-out time when sound stops 🎛️ set reverb of sound [MySound] to time (100) decay (100) mix (50) ::#666666 ``` +Adds reverb to an inputted sound. + +**Warning:** Initializing reverb can cause framerate drops as its heavy to setup. For dynamic on/off use, you can use the Delay effect +Parameters | What they Do +--- | --- +time | the length (or room space) of the reverb effect +decay | the rate of how long the reverb fades over time +mix | percentage of how dry or wet the sound is + ```scratch 🎛️ set delay of sound [MySound] to time (50) feedback (60) mix (50) ::#666666 ``` +Adds an echoing delay effect to a sound. + +Parameters | What they Do +--- | --- +time | interval between each echo +feedback | intensity of each subsequent echo +mix | percentage of the original sound and the delayed sound + ```scratch 🎛️ set tremolo of sound [MySound] to speed (35) depth (80) mix (100) ::#666666 ``` +Modulates the volume of a sound periodically, creating a tremolo effect. + +Parameters | What they Do +--- | --- +speed | how fast the volume fluctuates +depth | intensity of the volume variation. A higher depth means more dramatic volume changes +mix | amount of the tremolo effect applied + ```scratch 🎛️ set fuzz of sound [MySound] to low (60) med-low (50) med-high (80) high (60) mix (50) ::#666666 ``` +Applies a fuzzy distortion to a sound. + +Parameters | What they Do +--- | --- +low | amount of distortion applied to low frequencies +med-low | amount of distortion applied to mid-low frequencies +med-high | amount of distortion applied to mid-high frequencies +high | amount of distortion applied to high frequencies +mix | proportion of unfuzzed sound and fuzzed sound + ```scratch 🎛️ set bitcrush of sound [MySound] to bits (65) freq (60000) ::#666666 ``` +Reduces a sound's resolution and frequency, creating a chiptune-like, retro effect. + +Parameters | What they Do +--- | --- +bits | bit depth. Lower values result in more distortion +freq | sampling frequency. Lower values create a grittier effect + ```scratch 🎛️ set [highpass v] of sound [MySound] to frequency (400) peak (10) ::#666666 ``` +Highpass: Filters out lower frequencies below the specified cutoff, allowing higher frequencies to pass through. + +Lowpass: Filters out higher frequencies above the specified cutoff, allowing lower frequencies to pass through. + +Parameters | What they Do +--- | --- +frequency | cutoff frequency for the filter +peak | resonance at the cutoff frequency + ```scratch 🎛️ set flanger of sound [MySound] to time (45) speed (20) depth (10) feed (10) mix (50) ::#666666 ``` +Combines the original sound with a delayed version, creating a sweeping, "jet-like" sound. + +Parameters | What they Do +--- | --- +time | delay time for the effect +speed | modulation controlling how quickly it oscillates +depth | intensity of the effect +feed | feedback level, controlling how much flanged sound enters +mix | proportion of the original and flanged sound + ```scratch 🎛️ set compressor of sound [MySound] to threshold (15) knee (50) attack (50) release (50) ratio (50) ::#666666 ``` +Compresses the dynamic range of a sound, making quiet sounds louder and loud sounds quieter. + +Parameters | What they Do +--- | --- +threshold | volume level at which compression begins +knee | smoothness of the transition into compression +attack | how quickly the compressor responds to volume changes +release | how quickly the compression effect fades after the volume decreases +ratio | amount of compression applied + ```scratch 🎛️ set equalizer of sound [MySound] to gain low (100) med (100) high (100) cutoff low (-50) cutoff high (50) ::#666666 ``` + +Adjusts the balance between different frequency bands of the sound. This is a 3-Band Equalizer. + +Parameters | What they Do +--- | --- +gain low | gain applied to low frequencies +gain med | gain applied to mid frequencies +gain high | gain applied to high frequencies +cutoff low | low-frequency cutoff point +cutoff high | high-frequency cutoff point From 99277ba529b67ede47b3a48c61092e3e8971076b Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:43:54 -0800 Subject: [PATCH 38/40] Tune-Shark-V3.js -- add docs --- extensions/SharkPool/Tune-Shark-V3.js | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index 72b2ef12cc..b5e61ef2b8 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -190,6 +190,7 @@ return { id: "SPtuneShark3", name: Scratch.translate("Tune Shark V3"), + docsURI: "https://extensions.turbowarp.org/SharkPool/Tune-Shark-V3", color1: "#666666", menuIconURI, blockIconURI, From 13fdb3bf79bbc6d04ad513645b971bdad16aaea9 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:01:08 -0800 Subject: [PATCH 39/40] Tune-Shark-V3.svg -- Remove Text --- images/SharkPool/Tune-Shark-V3.svg | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/images/SharkPool/Tune-Shark-V3.svg b/images/SharkPool/Tune-Shark-V3.svg index ead191f404..d7201d3866 100644 --- a/images/SharkPool/Tune-Shark-V3.svg +++ b/images/SharkPool/Tune-Shark-V3.svg @@ -1,4 +1,3 @@ - @@ -52,11 +51,11 @@ + - @@ -93,8 +92,6 @@ - - From 4018990ac32e90492ccb6134d718b5627f6601d7 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Mon, 20 Jan 2025 21:02:09 -0800 Subject: [PATCH 40/40] Tune-Shark-V3.js -- all fixes --- extensions/SharkPool/Tune-Shark-V3.js | 86 ++++++++++++++++----------- 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/extensions/SharkPool/Tune-Shark-V3.js b/extensions/SharkPool/Tune-Shark-V3.js index b5e61ef2b8..280c8d4f35 100644 --- a/extensions/SharkPool/Tune-Shark-V3.js +++ b/extensions/SharkPool/Tune-Shark-V3.js @@ -28,7 +28,30 @@ extraIcons[key]; // Modified Pizzicato Library (Web Audio API, but with Premade Effects and Stuff) - // uses MIT License + /* + The MIT License (MIT) + + Copyright (c) 2016 Alejandro Mantecon Guillen + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + const scriptElement = document.createElement("script"); scriptElement.textContent = `!function(e){"use strict";var t={},i=t,n="object"==typeof module&&module.exports,o="function"==typeof define&&define.amd;n?module.exports=t:o?define([],t):e.Pizzicato=e.Pz=t;var s=e.AudioContext||e.webkitAudioContext;if(!s){console.error("No AudioContext found in this environment. Please ensure your window or global object contains a working AudioContext constructor function.");return}t.context=new s;var r=t.context.createGain();r.connect(t.context.destination),t.Util={isString:function(e){return"[object String]"===toString.call(e)},isObject:function(e){return"[object Object]"===toString.call(e)},isFunction:function(e){return"[object Function]"===toString.call(e)},isNumber:function(e){return"[object Number]"===toString.call(e)&&e===+e},isArray:function(e){return"[object Array]"===toString.call(e)},isInRange:function(e,t,n){return!!(i.Util.isNumber(e)&&i.Util.isNumber(t)&&i.Util.isNumber(n))&&e>=t&&e<=n},isBool:function(e){return"boolean"==typeof e},isOscillator:function(e){return e&&"[object OscillatorNode]"===e.toString()},isAudioBufferSourceNode:function(e){return e&&"[object AudioBufferSourceNode]"===e.toString()},isSound:function(e){return e instanceof i.Sound},isEffect:function(e){for(var i in t.Effects)if(e instanceof t.Effects[i])return!0;return!1},normalize:function(e,t,n){if(i.Util.isNumber(e)&&i.Util.isNumber(t)&&i.Util.isNumber(n))return(n-t)*e/1+t},getDryLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e<=.5?1:1-(e-.5)*2},getWetLevel:function(e){return!i.Util.isNumber(e)||e>1||e<0?0:e>=.5?1:1-(.5-e)*2}};var a=Object.getPrototypeOf(Object.getPrototypeOf(t.context.createGain())),c=a.connect;a.connect=function(e){var t=i.Util.isEffect(e)?e.inputNode:e;return c.call(this,t),e},Object.defineProperty(t,"volume",{enumerable:!0,get:function(){return r.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&r&&(r.gain.value=e)}}),Object.defineProperty(t,"masterGainNode",{enumerable:!1,get:function(){return r},set:function(e){console.error("Can't set the master gain node")}}),t.Events={on:function(e,t,i){e&&t&&(this._events=this._events||{},(this._events[e]||(this._events[e]=[])).push({callback:t,context:i||this,handler:this}))},trigger:function(e){var t,i,n,o;if(e){for(this._events=this._events||{},t=this._events[e]||(this._events[e]=[]),o=0,i=Math.max(0,arguments.length-1),n=[];o1){e.shift(),h(e,i);return}t=t||Error("Error decoding audio file "+e[0]),s.isFunction(i)&&i(t)}).bind(o))},n.onreadystatechange=function(t){4===n.readyState&&200!==n.status&&console.error("Error while fetching "+e[0]+". "+n.statusText)},n.send()}function u(e,i){var n=s.isFunction(e)?e:e.audioFunction,o=s.isObject(e)&&e.bufferSize?e.bufferSize:null;if(!o)try{t.context.createScriptProcessor()}catch(r){o=2048}this.getRawSourceNode=function(){var e=t.context.createScriptProcessor(o,1,1);return e.onaudioprocess=n,e}}this.detached=a&&e.options.detached,this.masterVolume=t.context.createGain(),this.fadeNode=t.context.createGain(),this.fadeNode.gain.value=0,this.detached||this.masterVolume.connect(t.masterGainNode),this.lastTimePlayed=0,this.effects=[],this.effectConnectors=[],this.playing=this.paused=!1,this.loop=a&&e.options.loop,this.attack=a&&s.isNumber(e.options.attack)?e.options.attack:.04,this.volume=a&&s.isNumber(e.options.volume)?e.options.volume:1,a&&s.isNumber(e.options.release)?this.release=e.options.release:a&&s.isNumber(e.options.sustain)?(console.warn("'sustain' is deprecated. Use 'release' instead."),this.release=e.options.sustain):this.release=.04,e?s.isString(e)?h.bind(this)(e,n):s.isFunction(e)?u.bind(this)(e,n):"file"===e.source?h.bind(this)(e.options.path,n):"buffer"===e.source?(function e(i,n){if((i=Array.isArray(i)?i:[i])[0]instanceof AudioBuffer){let s=i[0];o.getRawSourceNode=function(){let e=t.context.createBufferSource();return e.loop=this.loop,e.buffer=s,e},"function"==typeof n&&n()}}).bind(this)(e.options.buffer,n):"wave"===e.source?c.bind(this)(e.options,n):"input"===e.source?(function e(i,n){if(navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,!navigator.getUserMedia&&(!navigator.mediaDevices||navigator.mediaDevices.getUserMedia)){console.error("Your browser does not support getUserMedia. Note that the current document must be loaded securely for this to work");return}var r=(function(e){o.getRawSourceNode=function(){return t.context.createMediaStreamSource(e)},s.isFunction(n)&&n()}).bind(o),a=function(e){s.isFunction(n)&&n(e)};navigator.mediaDevices.getUserMedia?navigator.mediaDevices.getUserMedia({audio:!0}).then(r).catch(a):navigator.getUserMedia({audio:!0},r,a)}).bind(this)(e,n):"script"===e.source?u.bind(this)(e.options,n):"sound"===e.source&&(function e(t,n){this.getRawSourceNode=t.sound.getRawSourceNode,t.sound.sourceNode&&i.Util.isOscillator(t.sound.sourceNode)&&(this.sourceNode=this.getRawSourceNode(),this.frequency=t.sound.frequency)}).bind(this)(e.options,n):c.bind(this)({},n)},t.Sound.prototype=Object.create(t.Events,{play:{enumerable:!0,value:function(e,n){this.playing||(i.Util.isNumber(n)||(n=this.offsetTime||0),i.Util.isNumber(e)||(e=0),this.playing=!0,this.paused=!1,this.sourceNode=this.getSourceNode(),this.applyAttack(),i.Util.isFunction(this.sourceNode.start)&&(this.lastTimePlayed=t.context.currentTime-n,this.sourceNode.start(i.context.currentTime+e,n)),this.trigger("play"))}},stop:{enumerable:!0,value:function(){(this.paused||this.playing)&&(this.paused=this.playing=!1,this.stopWithRelease(),this.offsetTime=0,this.trigger("stop"))}},pause:{enumerable:!0,value:function(){if(!this.paused&&this.playing){this.paused=!0,this.playing=!1,this.stopWithRelease();var e=i.context.currentTime-this.lastTimePlayed;this.sourceNode.buffer?this.offsetTime=e%(this.sourceNode.buffer.length/i.context.sampleRate):this.offsetTime=e,this.trigger("pause")}}},clone:{enumerable:!0,value:function(){for(var e=new t.Sound({source:"sound",options:{loop:this.loop,attack:this.attack,release:this.release,volume:this.volume,sound:this}}),i=0;i0?this.effectConnectors[this.effectConnectors.length-1]:this.fadeNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this sound."),this;var n=this.playing;n&&this.pause();var o=0===i?this.fadeNode:this.effectConnectors[i-1];o.disconnect();var s=this.effectConnectors[i];return s.disconnect(),e.disconnect(s),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],o.connect(t),n&&this.play(),this}},connect:{enumerable:!0,value:function(e){return this.masterVolume.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.masterVolume.disconnect(e),this}},connectEffects:{enumerable:!0,value:function(){for(var e=[],t=0;t0?this.effects[0].inputNode:this.masterVolume}},applyAttack:{enumerable:!1,value:function(){if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.attack){this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,.001);return}var e=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,t=this.attack;e||(t=(1-this.fadeNode.gain.value)*this.attack),this.fadeNode.gain.setTargetAtTime(1,i.context.currentTime,2*t)}},stopWithRelease:{enumerable:!1,value:function(e){var t=this.sourceNode,n=function(){return i.Util.isFunction(t.stop)?t.stop(0):t.disconnect()};if(this.fadeNode.gain.value,this.fadeNode.gain.cancelScheduledValues(i.context.currentTime),!this.release){this.fadeNode.gain.setTargetAtTime(0,i.context.currentTime,.001),n();return}var o=navigator.userAgent.toLowerCase().indexOf("firefox")>-1,s=this.release;o||(s=this.fadeNode.gain.value*this.release),this.fadeNode.gain.setTargetAtTime(1e-5,i.context.currentTime,s/5),window.setTimeout(function(){n()},1e3*s)}}}),t.Group=function(e){e=e||[],this.mergeGainNode=i.context.createGain(),this.masterVolume=i.context.createGain(),this.sounds=[],this.effects=[],this.effectConnectors=[],this.mergeGainNode.connect(this.masterVolume),this.masterVolume.connect(i.masterGainNode);for(var t=0;t-1){console.warn("The Pizzicato.Sound object was already added to this group");return}if(e.detached){console.warn("Groups do not support detached sounds. You can manually create an audio graph to group detached sounds together.");return}e.disconnect(i.masterGainNode),e.connect(this.mergeGainNode),this.sounds.push(e)}},removeSound:{enumerable:!0,value:function(e){var t=this.sounds.indexOf(e);if(-1===t){console.warn("Cannot remove a sound that is not part of this group.");return}e.disconnect(this.mergeGainNode),e.connect(i.masterGainNode),this.sounds.splice(t,1)}},volume:{enumerable:!0,get:function(){if(this.masterVolume)return this.masterVolume.gain.value},set:function(e){i.Util.isInRange(e,0,1)&&(this.masterVolume.gain.value=e)}},play:{enumerable:!0,value:function(){for(var e=0;e0?this.effectConnectors[this.effectConnectors.length-1]:this.mergeGainNode;t.disconnect(),t.connect(e);var n=i.context.createGain();return this.effectConnectors.push(n),e.connect(n),n.connect(this.masterVolume),this}},removeEffect:{enumerable:!0,value:function(e){var t,i=this.effects.indexOf(e);if(-1===i)return console.warn("Cannot remove effect that is not applied to this group."),this;var n=0===i?this.mergeGainNode:this.effectConnectors[i-1];n.disconnect();var o=this.effectConnectors[i];return o.disconnect(),e.disconnect(o),this.effectConnectors.splice(i,1),this.effects.splice(i,1),t=i>this.effects.length-1||0===this.effects.length?this.masterVolume:this.effects[i],n.connect(t),this}}}),t.Effects={};var h=Object.create(null,{connect:{enumerable:!0,value:function(e){return this.outputNode.connect(e),this}},disconnect:{enumerable:!0,value:function(e){return this.outputNode.disconnect(e),this}}});function u(e,n){this.options={},e=e||this.options;var o={frequency:350,peak:1};for(var s in this.inputNode=this.filterNode=i.context.createBiquadFilter(),this.filterNode.type=n,this.outputNode=t.context.createGain(),this.filterNode.connect(this.outputNode),o)this[s]=e[s],this[s]=void 0===this[s]||null===this[s]?o[s]:this[s]}t.Effects.Delay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.delayNode),this.inputNode.connect(this.delayNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Delay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Compressor=function(e){this.options={},e=e||this.options;var i={threshold:-24,knee:30,attack:.003,release:.25,ratio:12};for(var n in this.inputNode=this.compressorNode=t.context.createDynamicsCompressor(),this.outputNode=t.context.createGain(),this.compressorNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Compressor.prototype=Object.create(h,{threshold:{enumerable:!0,get:function(){return this.compressorNode.threshold.value},set:function(e){t.Util.isInRange(e,-100,0)&&(this.compressorNode.threshold.value=e)}},knee:{enumerable:!0,get:function(){return this.compressorNode.knee.value},set:function(e){t.Util.isInRange(e,0,40)&&(this.compressorNode.knee.value=e)}},attack:{enumerable:!0,get:function(){return this.compressorNode.attack.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.attack.value=e)}},release:{enumerable:!0,get:function(){return this.compressorNode.release.value},set:function(e){t.Util.isInRange(e,0,1)&&(this.compressorNode.release.value=e)}},ratio:{enumerable:!0,get:function(){return this.compressorNode.ratio.value},set:function(e){t.Util.isInRange(e,1,20)&&(this.compressorNode.ratio.value=e)}},getCurrentGainReduction:function(){return this.compressorNode.reduction}}),t.Effects.LowPassFilter=function(e){u.call(this,e,"lowpass")},t.Effects.HighPassFilter=function(e){u.call(this,e,"highpass")};var d=Object.create(h,{frequency:{enumerable:!0,get:function(){return this.filterNode.frequency.value},set:function(e){t.Util.isInRange(e,10,22050)&&(this.filterNode.frequency.value=e)}},peak:{enumerable:!0,get:function(){return this.filterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.filterNode.Q.value=e)}}});function l(){var e,n,o=i.context.sampleRate*this.time,s=t.context.createBuffer(2,o,i.context.sampleRate),r=s.getChannelData(0),a=s.getChannelData(1);for(n=0;n-1?this.pannerNode.pan.value=e:this.pannerNode.setPosition(e,0,1-Math.abs(e))))}}}),t.Effects.Convolver=function(e,n){this.options={},e=e||this.options;var o=this,s=new XMLHttpRequest,r={mix:.5};for(var a in this.callback=n,this.inputNode=t.context.createGain(),this.convolverNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.convolverNode),this.convolverNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),r)this[a]=e[a],this[a]=void 0===this[a]||null===this[a]?r[a]:this[a];if(!e.impulse){console.error("No impulse file specified.");return}s.open("GET",e.impulse,!0),s.responseType="arraybuffer",s.onload=function(e){var n=e.target.response;t.context.decodeAudioData(n,function(e){o.convolverNode.buffer=e,o.callback&&i.Util.isFunction(o.callback)&&o.callback()},function(e){e=e||Error("Error decoding impulse file"),o.callback&&i.Util.isFunction(o.callback)&&o.callback(e)})},s.onreadystatechange=function(t){4===s.readyState&&200!==s.status&&console.error("Error while fetching "+e.impulse+". "+s.statusText)},s.send()},t.Effects.Convolver.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}}}),t.Effects.PingPongDelay=function(e){this.options={},e=e||this.options;var i={feedback:.5,time:.3,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.delayNodeLeft=t.context.createDelay(),this.delayNodeRight=t.context.createDelay(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.channelMerger=t.context.createChannelMerger(2),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.delayNodeLeft.connect(this.channelMerger,0,0),this.delayNodeRight.connect(this.channelMerger,0,1),this.delayNodeLeft.connect(this.delayNodeRight),this.feedbackGainNode.connect(this.delayNodeLeft),this.delayNodeRight.connect(this.feedbackGainNode),this.inputNode.connect(this.feedbackGainNode),this.channelMerger.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.PingPongDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNodeLeft.delayTime.value=e,this.delayNodeRight.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}}}),t.Effects.Reverb=function(e){this.options={},e=e||this.options;var i={mix:.5,time:.01,decay:.01,reverse:!1};for(var n in this.inputNode=t.context.createGain(),this.reverbNode=t.context.createConvolver(),this.outputNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.inputNode.connect(this.reverbNode),this.reverbNode.connect(this.wetGainNode),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n];l.bind(this)()},t.Effects.Reverb.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.time=e,l.bind(this)())}},decay:{enumerable:!0,get:function(){return this.options.decay},set:function(e){i.Util.isInRange(e,1e-4,10)&&(this.options.decay=e,l.bind(this)())}},reverse:{enumerable:!0,get:function(){return this.options.reverse},set:function(e){i.Util.isBool(e)&&(this.options.reverse=e,l.bind(this)())}}}),t.Effects.Bitcrusher=function(e){this.inputNode=i.context.createGain(),this.outputNode=i.context.createGain(),this.bits=e.bits||4,this.frequency=e.frequency||44100,this.crusherNode=i.context.createScriptProcessor(4096,1,1);var t=this;this.crusherNode.onaudioprocess=function(e){for(var n=e.inputBuffer,o=e.outputBuffer,s=0;s=1&&e<=16&&(this.bits=e)},getBits:function(){return this.bits},setFrequency:function(e){e>0&&(this.frequency=e)},getFrequency:function(){return this.frequency}},t.Effects.ThreeBandEqualizer=function(e){f.call(this,e)};var p=Object.create(h,{cutoff_frequency_low:{enumerable:!0,get:function(){return this.options.cutoff_frequency_low},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_low=e,this.lowFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},cutoff_frequency_high:{enumerable:!0,get:function(){return this.options.cutoff_frequency_high},set:function(e){t.Util.isInRange(e,10,22050)&&(this.options.cutoff_frequency_high=e,this.highFilterNode.frequency.value=e,this.midFilterNode.frequency.value=.707*(this.options.cutoff_frequency_low+this.options.cutoff_frequency_high))}},low_band_gain:{enumerable:!0,get:function(){return this.options.low_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.low_band_gain=e,this.lowGainNode.gain.value=Math.pow(10,e/20))}},mid_band_gain:{enumerable:!0,get:function(){return this.options.mid_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.mid_band_gain=e,this.midGainNode.gain.value=Math.pow(10,e/20))}},high_band_gain:{enumerable:!0,get:function(){return this.options.high_band_gain},set:function(e){t.Util.isInRange(e,-40,15)&&(this.options.high_band_gain=e,this.highGainNode.gain.value=Math.pow(10,e/20))}},low_peak:{enumerable:!0,get:function(){return this.lowFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.lowFilterNode.Q.value=e)}},mid_peak:{enumerable:!0,get:function(){return this.midFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,100)&&(this.midFilterNode.Q.value=e)}},high_peak:{enumerable:!0,get:function(){return this.highFilterNode.Q.value},set:function(e){t.Util.isInRange(e,1e-4,1e3)&&(this.highFilterNode.Q.value=e)}},visualizerBinCount:{enumerable:!0,get:function(){return this.analyserNode.frequencyBinCount},set:function(e){t.Util.isInRange(e,16,1024)&&(this.analyzerNode.fftSize=e)}},analyser:{enumerable:!0,get:function(){return this.analyserNode}},frequencyData:{enumerable:!0,get:function(){return void 0===this.byteFrequencyData&&(this.byteFrequencyData=new Uint8Array(this.analyserNode.frequencyBinCount.value)),this.analyserNode.getByteFrequencyData(this.FrequencyData),this.byteFrequencyData}}});t.Effects.ThreeBandEqualizer.prototype=p,t.Effects.Tremolo=function(e){this.options={},e=e||this.options;var i={speed:4,depth:1,mix:.8};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.tremoloGainNode=t.context.createGain(),this.tremoloGainNode.gain.value=0,this.lfoNode=t.context.createOscillator(),this.shaperNode=t.context.createWaveShaper(),this.shaperNode.curve=new Float32Array([0,1]),this.shaperNode.connect(this.tremoloGainNode.gain),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.lfoNode.connect(this.shaperNode),this.lfoNode.type="sine",this.lfoNode.start(0),this.inputNode.connect(this.tremoloGainNode),this.tremoloGainNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.Tremolo.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},speed:{enumerable:!0,get:function(){return this.options.speed},set:function(e){i.Util.isInRange(e,0,20)&&(this.options.speed=e,this.lfoNode.frequency.value=e)}},depth:{enumerable:!0,get:function(){return this.options.depth},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.depth=e,this.shaperNode.curve=new Float32Array([1-e,1]))}}}),t.Effects.DubDelay=function(e){this.options={},e=e||this.options;var i={feedback:.6,time:.7,mix:.5,cutoff:700};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.feedbackGainNode=t.context.createGain(),this.delayNode=t.context.createDelay(),this.bqFilterNode=t.context.createBiquadFilter(),this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.wetGainNode),this.inputNode.connect(this.feedbackGainNode),this.feedbackGainNode.connect(this.bqFilterNode),this.bqFilterNode.connect(this.delayNode),this.delayNode.connect(this.feedbackGainNode),this.delayNode.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]},t.Effects.DubDelay.prototype=Object.create(h,{mix:{enumerable:!0,get:function(){return this.options.mix},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.mix=e,this.dryGainNode.gain.value=t.Util.getDryLevel(this.mix),this.wetGainNode.gain.value=t.Util.getWetLevel(this.mix))}},time:{enumerable:!0,get:function(){return this.options.time},set:function(e){i.Util.isInRange(e,0,180)&&(this.options.time=e,this.delayNode.delayTime.value=e)}},feedback:{enumerable:!0,get:function(){return this.options.feedback},set:function(e){i.Util.isInRange(e,0,1)&&(this.options.feedback=parseFloat(e,10),this.feedbackGainNode.gain.value=this.feedback)}},cutoff:{enumerable:!0,get:function(){return this.options.cutoff},set:function(e){i.Util.isInRange(e,0,4e3)&&(this.options.cutoff=e,this.bqFilterNode.frequency.value=this.cutoff)}}}),t.Effects.RingModulator=function(e){this.options={},e=e||this.options;var i={speed:30,distortion:1,mix:.5};for(var n in this.inputNode=t.context.createGain(),this.outputNode=t.context.createGain(),this.dryGainNode=t.context.createGain(),this.wetGainNode=t.context.createGain(),this.vIn=t.context.createOscillator(),this.vIn.start(0),this.vInGain=t.context.createGain(),this.vInGain.gain.value=.5,this.vInInverter1=t.context.createGain(),this.vInInverter1.gain.value=-1,this.vInInverter2=t.context.createGain(),this.vInInverter2.gain.value=-1,this.vInDiode1=new N(t.context),this.vInDiode2=new N(t.context),this.vInInverter3=t.context.createGain(),this.vInInverter3.gain.value=-1,this.vcInverter1=t.context.createGain(),this.vcInverter1.gain.value=-1,this.vcDiode3=new N(t.context),this.vcDiode4=new N(t.context),this.outGain=t.context.createGain(),this.outGain.gain.value=3,this.compressor=t.context.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.ratio.value=16,this.inputNode.connect(this.dryGainNode),this.dryGainNode.connect(this.outputNode),this.inputNode.connect(this.vcInverter1),this.inputNode.connect(this.vcDiode4.node),this.vcInverter1.connect(this.vcDiode3.node),this.vIn.connect(this.vInGain),this.vInGain.connect(this.vInInverter1),this.vInGain.connect(this.vcInverter1),this.vInGain.connect(this.vcDiode4.node),this.vInInverter1.connect(this.vInInverter2),this.vInInverter1.connect(this.vInDiode2.node),this.vInInverter2.connect(this.vInDiode1.node),this.vInDiode1.connect(this.vInInverter3),this.vInDiode2.connect(this.vInInverter3),this.vInInverter3.connect(this.compressor),this.vcDiode3.connect(this.compressor),this.vcDiode4.connect(this.compressor),this.compressor.connect(this.outGain),this.outGain.connect(this.wetGainNode),this.wetGainNode.connect(this.outputNode),i)this[n]=e[n],this[n]=void 0===this[n]||null===this[n]?i[n]:this[n]};var N=function(e){this.context=e,this.node=this.context.createWaveShaper(),this.vb=.2,this.vl=.4,this.h=1,this.setCurve()};function v(e){for(var t=i.context.sampleRate,n=new Float32Array(t),o=Math.PI/180,s=0;sr;e=0<=r?++s:--s)n=(i=Math.abs(i=(e-t/2)/(t/2)))<=this.vb?0:this.vb - this.loadStorage(runtime.extensionStorage["SPtuneShark3"]) - ); - } + runtime.on("PROJECT_LOADED", () => + this.loadStorage(runtime.extensionStorage["SPtuneShark3"]) + ); runtime.on("PROJECT_START", () => { if (settings.flagCtrl) this.ctrlSounds({ CONTROL: "stop" }); @@ -1170,21 +1191,22 @@ } ctrlSounds(args) { + const allSounds = Object.values(soundBank); if (args.CONTROL === "start") - Object.keys(soundBank).forEach((key) => { - this.play(soundBank[key].context, 0, soundBank[key]); + allSounds.forEach((sound) => { + this.play(sound.context, 0, sound); }); else if (args.CONTROL === "stop") - Object.keys(soundBank).forEach((key) => { - soundBank[key].context.stop(); + allSounds.forEach((sound) => { + sound.context.stop(); }); else if (args.CONTROL === "pause") - Object.keys(soundBank).forEach((key) => { - this.typeOverlay(soundBank[key], "pause"); + allSounds.forEach((sound) => { + this.typeOverlay(sound, "pause"); }); else - Object.keys(soundBank).forEach((key) => { - this.typeOverlay(soundBank[key], "unpause"); + allSounds.forEach((sound) => { + this.typeOverlay(sound, "unpause"); }); } @@ -1596,31 +1618,23 @@ save2Project(args) { settings.canSave = args.SAVE === "save"; - if (!Scratch.extensions.isPenguinMod) { - if (settings.canSave) { - const convertBank = JSON.parse(JSON.stringify(soundBank)); - Object.values(convertBank).forEach((item) => delete item.context); - runtime.extensionStorage["SPtuneShark3"] = { - bank: convertBank, - settings, - }; - } else { - runtime.extensionStorage["SPtuneShark3"] = undefined; - } - } - } - - // PenguinMod Storage - serialize() { if (settings.canSave) { - const convertBank = JSON.parse(JSON.stringify(soundBank)); - Object.values(convertBank).forEach((item) => delete item.context); - return { SPtuneShark3: { bank: convertBank, settings } }; + const convertedBank = {}; + Object.entries(soundBank).forEach((item) => { + const soundData = {}; + Object.entries(item[1]).forEach((data) => { + if (data[0] !== "context") soundData[data[0]] = data[1]; + }); + convertedBank[item[0]] = soundData; + }); + runtime.extensionStorage["SPtuneShark3"] = { + bank: convertedBank, + settings, + }; + } else { + runtime.extensionStorage["SPtuneShark3"] = undefined; } } - deserialize(data) { - this.loadStorage(data.SPtuneShark3); - } } Scratch.extensions.register(new SPtuneShark3());