diff --git a/.gitignore b/.gitignore
index c986044..094b12d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ node_modules/
out/
tmp/
assets/
+public/*.js
audio/**/*_data/
*.aup
*.tgz
diff --git a/README.md b/README.md
index 3f66c3a..563e6ed 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,6 @@ Prerequisites:
Initial setup:
* `npm install`
-* `npm run assets`
Run:
diff --git a/index.html b/index.html
index 4f0b10e..4f60a13 100644
--- a/index.html
+++ b/index.html
@@ -1,167 +1,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
- Help
-
-
- Click in a box to add or remove a note.
- Shift click to extend a note, and ctrl-shift click to shorten it.
- Press the space bar to play or stop the song.
- After clicking in an entry field, use the arrow keys to quickly change the value.
-
-
-
- Close
-
-
+
diff --git a/package-lock.json b/package-lock.json
index 45c3a05..67cb007 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33,6 +33,629 @@
"vary": "^1.1.2"
}
},
+ "@material/animation": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@material/animation/-/animation-1.0.0.tgz",
+ "integrity": "sha512-Ed5/vggn6ZhSJ87yn3ZS1d826VJNFz73jHF2bSsgRtHDoB8KCuOwQMfdgAgDa4lKDF6CDIPCKBZPKrs2ubehdw==",
+ "requires": {
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/base": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@material/base/-/base-1.0.0.tgz",
+ "integrity": "sha512-5dxFp46x5FA+Epg6YHLzN+5zRt9S2wR84UdvVAEJ1egea94m9UHUg7y9tAnNSN16aexRSywmzyLwPr+i8PGEYA==",
+ "requires": {
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/button": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@material/button/-/button-1.1.1.tgz",
+ "integrity": "sha512-03aEyzZBIeqpgZkqLjro/enz8ORUnfQslBUdAgkPqdjh1X0oIEugr3UaFyC5QlSBTU3j3GIsnKIxWaggkRenpQ==",
+ "requires": {
+ "@material/elevation": "^1.1.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/ripple": "^1.1.0",
+ "@material/rtl": "^0.42.0",
+ "@material/shape": "^1.1.1",
+ "@material/theme": "^1.1.0",
+ "@material/typography": "^1.0.0"
+ }
+ },
+ "@material/checkbox": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-2.3.0.tgz",
+ "integrity": "sha512-ejDn0CyXITF8mKcBZHCLa0fc0a/agej9o4viMLOXXkUIfOY3NJuY11MyZ07MxAgin11NK9HpUX7cIxu3upV+6w==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/dom": "^1.1.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/ripple": "^2.3.0",
+ "@material/rtl": "^0.42.0",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ },
+ "dependencies": {
+ "@material/ripple": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-2.3.0.tgz",
+ "integrity": "sha512-ejXR0nstERofFhssRyFlwOLgebwm2uGbarHtWZ2/+7QY2Th/Z1wOqNb2h/WRoShsJXK11RUsochb6BJrg30u7w==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/dom": "^1.1.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ }
+ }
+ },
+ "@material/dialog": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-2.3.0.tgz",
+ "integrity": "sha512-T0xEKUW4uY5ZK7S92bdCVjFo9naasTRFNTXccIk+lBkbUlTsCOJMtix+dSJcj8ba8NOZjjq7phfAQqnNKUOhvg==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/dom": "^1.1.0",
+ "@material/elevation": "^1.1.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/ripple": "^2.3.0",
+ "@material/rtl": "^0.42.0",
+ "@material/shape": "^1.1.1",
+ "@material/theme": "^1.1.0",
+ "@material/typography": "^2.3.0",
+ "focus-trap": "^5.0.0",
+ "tslib": "^1.9.3"
+ },
+ "dependencies": {
+ "@material/ripple": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-2.3.0.tgz",
+ "integrity": "sha512-ejXR0nstERofFhssRyFlwOLgebwm2uGbarHtWZ2/+7QY2Th/Z1wOqNb2h/WRoShsJXK11RUsochb6BJrg30u7w==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/dom": "^1.1.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/typography": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/typography/-/typography-2.3.0.tgz",
+ "integrity": "sha512-NtWVVvwG9Te6/kuIl4fEwDcXGCS7mfPgo5CKPyxcK6y0hJHv6yRHpipJT9D4ZlXw0sQx9B33doOK7iYJtwBBZw==",
+ "requires": {
+ "@material/feature-targeting": "^0.44.1"
+ }
+ }
+ }
+ },
+ "@material/dom": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@material/dom/-/dom-1.1.0.tgz",
+ "integrity": "sha512-+HWW38ZaM2UBPu4+7QCusLDSf4tFT31rsEXHkTkxYSg/QpDivfPx6YDz4OmYtafmhPR1d1YjqB3MYysUHdodyw==",
+ "requires": {
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/elevation": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-1.1.0.tgz",
+ "integrity": "sha512-m4eATJvDhWK1BT+yA1iHz5mhAk8cV9olC4mjVzm4PTAqhDH2yya4WzjN1HPVHE/a65ObyZ7V4qopxu9MRocm3A==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/theme": "^1.1.0"
+ }
+ },
+ "@material/feature-targeting": {
+ "version": "0.44.1",
+ "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-0.44.1.tgz",
+ "integrity": "sha512-90cc7njn4aHbH9UxY8qgZth1W5JgOgcEdWdubH1t7sFkwqFxS5g3zgxSBt46TygFBVIXNZNq35Xmg80wgqO7Pg=="
+ },
+ "@material/floating-label": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-1.1.0.tgz",
+ "integrity": "sha512-7q7V+9o9XesgMnK11up9z+BcRFwtLIAIqVTCL3liKRARNHuzw9FGrGMKhTJUKvLZ3z0bM1+FmmVlA3q9FJWehQ==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/rtl": "^0.42.0",
+ "@material/theme": "^1.1.0",
+ "@material/typography": "^1.0.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/form-field": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-0.41.0.tgz",
+ "integrity": "sha512-vNduTfxS1KHCt/NATfX56m7iSXqcemrDq3NMX0txijUQyZ3Sr4xdUQdys+2ky/rBuQTVqBBsc9ixIyHehECaoQ==",
+ "requires": {
+ "@material/base": "^0.41.0",
+ "@material/rtl": "^0.40.1",
+ "@material/selection-control": "^0.41.0",
+ "@material/theme": "^0.41.0",
+ "@material/typography": "^0.41.0"
+ },
+ "dependencies": {
+ "@material/base": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@material/base/-/base-0.41.0.tgz",
+ "integrity": "sha512-tEyzwBRu3d1H120SfKsDVYZHcqT5lKohh/7cWKR93aAaPDkSvjpKJIjyu2yuSkjpDduVZGzVocYbOvhUKhhzXQ=="
+ },
+ "@material/rtl": {
+ "version": "0.40.1",
+ "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-0.40.1.tgz",
+ "integrity": "sha512-Pk6Iw1/KrhWZoZtkDsPMDUW0bm7Z1zeXb3MTQRCFmjf1wU5cRxgOTtuoZLcJqlcKGppLAzJL/TJV3E7KEiuL0A=="
+ },
+ "@material/theme": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@material/theme/-/theme-0.41.0.tgz",
+ "integrity": "sha512-ohW2JxObKOWvP34EkIIcrEVtL3g0Gs/T3/MdOsM36euyshY8Jwl1f6fjVUQvVjSpixUtSb30/+ulblF8fTOwBg=="
+ },
+ "@material/typography": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@material/typography/-/typography-0.41.0.tgz",
+ "integrity": "sha512-15dlqSU+9uGcWdg4KXXcmDzTKJPb7/5Z9kmooONb2Laot1uiuntDXQS0yL+U2FYLW5Ros+WVMosDBKFruWx68A=="
+ }
+ }
+ },
+ "@material/line-ripple": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-1.1.0.tgz",
+ "integrity": "sha512-XqCxDNfgkh9zq0IVlTEFVjmQV8hx8m4vxLFM5qwHDDqcKPlX/Lfc8M43fmm9uE1CaJBC6whMGPvOt/dIla+RUg==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/list": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@material/list/-/list-1.1.1.tgz",
+ "integrity": "sha512-YqX2A5qagoBolla6WHzP2BIUid/ufot5rVP2yrTz3DFvmswJMXU3HV2XU9NbiVOiefCjkra9ljtimiTlHUrAEg==",
+ "requires": {
+ "@material/base": "^1.0.0",
+ "@material/dom": "^1.1.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/ripple": "^1.1.0",
+ "@material/rtl": "^0.42.0",
+ "@material/shape": "^1.1.1",
+ "@material/theme": "^1.1.0",
+ "@material/typography": "^1.0.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/menu": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@material/menu/-/menu-1.1.1.tgz",
+ "integrity": "sha512-bfsZ4Uexm02ey665SukjMK+3HtQVu7Bbc/q5chvPmLoMbSQ4PYE19C/p8cXKeNxkhd3S0h/gJDPw/cTbB9mrhg==",
+ "requires": {
+ "@material/base": "^1.0.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/list": "^1.1.1",
+ "@material/menu-surface": "^1.1.1",
+ "@material/ripple": "^1.1.0",
+ "@material/rtl": "^0.42.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/menu-surface": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-1.1.1.tgz",
+ "integrity": "sha512-bOY3LsVamovl/yb4hMBDi3gh8UFEYyP3GHNpTt+X5KBPDehoFhXG9s21aNvGbHZbwURhhWiRzy/OUC3MLE/hKA==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/elevation": "^1.1.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/rtl": "^0.42.0",
+ "@material/shape": "^1.1.1",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/notched-outline": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-1.1.1.tgz",
+ "integrity": "sha512-HekxMWgIEGlmmdiCATfEJPjAWz2jlyXnfGUiBOkAzI25/OyOgcCd3rLzcMT5DUqItbKoNk1M/9kOmzTSNSt/CA==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/floating-label": "^1.1.0",
+ "@material/rtl": "^0.42.0",
+ "@material/shape": "^1.1.1",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/radio": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@material/radio/-/radio-1.1.0.tgz",
+ "integrity": "sha512-ySocik+l1fInopaA8hDfByJahv1UywUNtUcG7+hLLOhxQx4XVpUJUIjU/dlvVDYJB0QZQatm2asKi2cYVPlIsQ==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/ripple": "^1.1.0",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/react-button": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-button/-/react-button-0.14.1.tgz",
+ "integrity": "sha512-ZgBXykYhi71/s4qUP3PA3nj+TMLXJH2eGvmeIU2grNwxuYJSmg0uu9q/ZNG88nF7UBLOMBfvBxYJ8eUeMBUm3A==",
+ "requires": {
+ "@material/button": "^1.1.0",
+ "@material/react-ripple": "^0.14.1",
+ "classnames": "^2.2.6"
+ }
+ },
+ "@material/react-checkbox": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-checkbox/-/react-checkbox-0.14.1.tgz",
+ "integrity": "sha512-M5MlizhbgxoZd68KkudRJerzpUQ4bMlCC9o2WowW3QHViAUivrDOHZ27uW+sOIr5fTbY8RFqCcRCxZDFLKyDtw==",
+ "requires": {
+ "@material/checkbox": "^2.1.1",
+ "@material/react-ripple": "^0.14.1",
+ "classnames": "^2.2.6"
+ }
+ },
+ "@material/react-dialog": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@material/react-dialog/-/react-dialog-0.15.0.tgz",
+ "integrity": "sha512-E65/qxAb+BHy0RbPcyOpp4x3qrdgQeCTzPbCtz6r4+XcwPI4/7NVGaZQeFJKnlHsFe5nULZQznvCgn09p/1uVg==",
+ "requires": {
+ "@material/dialog": "^2.2.0",
+ "@material/dom": "^1.0.0",
+ "@material/react-button": "^0.15.0",
+ "classnames": "^2.2.6",
+ "focus-trap": "^5.0.0"
+ },
+ "dependencies": {
+ "@material/react-button": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@material/react-button/-/react-button-0.15.0.tgz",
+ "integrity": "sha512-OnnQx1XEomYlmp9TQIRYy+UAzJG1NPlmBfX/oP6hkxB4bWcrR2MLbqR4J1RMLDjl7a6qfdU2HskWPYp0wS+4Pw==",
+ "requires": {
+ "@material/button": "^1.1.0",
+ "@material/react-ripple": "^0.15.0",
+ "classnames": "^2.2.6"
+ }
+ },
+ "@material/react-ripple": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@material/react-ripple/-/react-ripple-0.15.0.tgz",
+ "integrity": "sha512-8YPEpiQNOaq3QtMc1DBxVVqOg/XnoWDj/EJ8eW0xmB4HvXElztKNTZoIpAAuQMZ8jbyZkRJRiFDa33u+wLFDCg==",
+ "requires": {
+ "@material/dom": "^1.0.0",
+ "@material/ripple": "^1.0.0",
+ "classnames": "^2.2.6",
+ "utility-types": "^3.2.1"
+ }
+ }
+ }
+ },
+ "@material/react-floating-label": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-floating-label/-/react-floating-label-0.14.1.tgz",
+ "integrity": "sha512-f6irfAi25DqmPDSYJULfF+CnyHgBeV/L/jIxRQ/AlJ95VeSJMsvOHXGhyDGYTThHwB8ZRQ8gU7xyRuC1xeRyqw==",
+ "requires": {
+ "@material/floating-label": "^1.0.0",
+ "classnames": "^2.2.6"
+ }
+ },
+ "@material/react-line-ripple": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-line-ripple/-/react-line-ripple-0.14.1.tgz",
+ "integrity": "sha512-P9hmd8NKFUAI5rWVaO+2bp4t5rHOTNGzlptDLubohjvyl1dcetnAr2tKwWztqIg5u+r8A6QF373HyiaSEM3dvA==",
+ "requires": {
+ "@material/line-ripple": "^1.0.0",
+ "classnames": "^2.2.6"
+ }
+ },
+ "@material/react-list": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-list/-/react-list-0.14.1.tgz",
+ "integrity": "sha512-3lbh7+GO8CS3cqWE6G7q7qscMlmHhuEkoagRw8gLghd7tkxB1p9t9zzjCSHf5mvDQDESw2NC6RX37tERwMPUiA==",
+ "requires": {
+ "@material/dom": "^1.1.0",
+ "@material/list": "^1.0.0",
+ "@material/react-checkbox": "^0.14.1",
+ "@material/react-radio": "^0.14.1",
+ "@material/react-ripple": "^0.14.1",
+ "classnames": "^2.2.6",
+ "memoize-one": "^5.0.4"
+ }
+ },
+ "@material/react-menu": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-menu/-/react-menu-0.14.1.tgz",
+ "integrity": "sha512-s/WgE3zGGUzN4b387Co4HE5J5OOmMm8Uq/se1smkxvrnkYVHwloFe2gqWiupPw2+od6/JuSt0CP2jy0Ja6EhRA==",
+ "requires": {
+ "@material/menu": "^1.1.0",
+ "@material/react-list": "^0.14.1",
+ "@material/react-menu-surface": "^0.14.1",
+ "classnames": "^2.2.6"
+ },
+ "dependencies": {
+ "@material/react-menu-surface": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-menu-surface/-/react-menu-surface-0.14.1.tgz",
+ "integrity": "sha512-Fb9p4/h9lAWSEXqzlMUhaIG39p6b3QILVY2WISBekekC5FS8fPtw9OhEDk2mzKu41Sr1bvdAXSNXwTpUQTpk1A==",
+ "requires": {
+ "@material/menu-surface": "^1.0.1",
+ "classnames": "^2.2.6"
+ }
+ }
+ }
+ },
+ "@material/react-menu-surface": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@material/react-menu-surface/-/react-menu-surface-0.15.0.tgz",
+ "integrity": "sha512-5oRXOSycRRoIE+H9ZAFQKWhoffkMbnEiT9PscFsHLqCe2jWfsRTRxGfImrQIc/Of5jSU947djB2clBtpgNGRbQ==",
+ "requires": {
+ "@material/menu-surface": "^1.0.1",
+ "classnames": "^2.2.6"
+ }
+ },
+ "@material/react-notched-outline": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-notched-outline/-/react-notched-outline-0.14.1.tgz",
+ "integrity": "sha512-vGRd8VoUimjLswhrO6BWZ9SSnv/YcquRpH8ZzZKTDm08bSjIO9kpD+OGqYzCJzq+zEN88VhHJo8Rje7rWpLyQw==",
+ "requires": {
+ "@material/notched-outline": "^1.1.1",
+ "classnames": "^2.2.6"
+ }
+ },
+ "@material/react-radio": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-radio/-/react-radio-0.14.1.tgz",
+ "integrity": "sha512-xPo3OEjelMgWk9ow6K9Hz2K2zQ7AD/cqbY8Oe0KZU715SP94cREyZ0VrfIrT/tWETHxEpyJ5z2+XRYPFvCwzMw==",
+ "requires": {
+ "@material/form-field": "^0.41.0",
+ "@material/radio": "^1.1.0",
+ "@material/react-ripple": "^0.14.1",
+ "classnames": "^2.2.6"
+ }
+ },
+ "@material/react-ripple": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-ripple/-/react-ripple-0.14.1.tgz",
+ "integrity": "sha512-UWrE6u7WiGihIB1eQfXr7pt2ZNLhphN4AQayz2x1ZTvKuQ4etGOyRkFeZcaBuv0Qb5f8UE76YsO+4yVUQsKGqQ==",
+ "requires": {
+ "@material/dom": "^1.0.0",
+ "@material/ripple": "^1.0.0",
+ "classnames": "^2.2.6",
+ "utility-types": "^3.2.1"
+ }
+ },
+ "@material/react-select": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-select/-/react-select-0.14.1.tgz",
+ "integrity": "sha512-naj6try0ygA0RN0CbTuOMKYh8e9NOCM9ieH6kCIBnK7iGfgqZU3xcZUhjxfETWBs6YU6R+a9dI0I7GYt4iYqPw==",
+ "requires": {
+ "@material/react-floating-label": "^0.14.1",
+ "@material/react-line-ripple": "^0.14.1",
+ "@material/react-menu": "^0.14.1",
+ "@material/react-menu-surface": "^0.14.1",
+ "@material/react-notched-outline": "^0.14.1",
+ "@material/select": "^1.1.1",
+ "classnames": "^2.2.6"
+ },
+ "dependencies": {
+ "@material/react-menu-surface": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-menu-surface/-/react-menu-surface-0.14.1.tgz",
+ "integrity": "sha512-Fb9p4/h9lAWSEXqzlMUhaIG39p6b3QILVY2WISBekekC5FS8fPtw9OhEDk2mzKu41Sr1bvdAXSNXwTpUQTpk1A==",
+ "requires": {
+ "@material/menu-surface": "^1.0.1",
+ "classnames": "^2.2.6"
+ }
+ }
+ }
+ },
+ "@material/react-text-field": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/@material/react-text-field/-/react-text-field-0.14.1.tgz",
+ "integrity": "sha512-xNzwhlyuKprh1/Qw1+vNA3+EIl+Z79GnseVp4rZyH8pTTOHg+mzPWkGmru7CEWK4SvsCmovb0CBCv66e0Dvt1g==",
+ "requires": {
+ "@material/react-floating-label": "^0.14.1",
+ "@material/react-line-ripple": "^0.14.1",
+ "@material/react-notched-outline": "^0.14.1",
+ "@material/textfield": "^2.3.1",
+ "classnames": "^2.2.6"
+ }
+ },
+ "@material/ripple": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-1.1.0.tgz",
+ "integrity": "sha512-mkfDBZAmxjpRG7V9TrfOmLxt1g/wvGHCXtYPgvH7W8ozjf53edqxLOFENEdvHbie27y9nyixzXn0gzU0HnxSeA==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/dom": "^1.1.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/rtl": {
+ "version": "0.42.0",
+ "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-0.42.0.tgz",
+ "integrity": "sha512-VrnrKJzhmspsN8WXHuxxBZ69yM5IwhCUqWr1t1eNfw3ZEvEj7i1g3P31HGowKThIN1dc1Wh4LE14rCISWCtv5w=="
+ },
+ "@material/select": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@material/select/-/select-1.1.1.tgz",
+ "integrity": "sha512-si/RGmqRcLflBmA4EXeiR4gVuOIM0YAqIwVN7WOwrx+AhfBDuvI9sPMDz2UXZ2VoFpll73TTRMdah2MPTpmCxQ==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/floating-label": "^1.1.0",
+ "@material/line-ripple": "^1.1.0",
+ "@material/menu": "^1.1.1",
+ "@material/menu-surface": "^1.1.1",
+ "@material/notched-outline": "^1.1.1",
+ "@material/ripple": "^1.1.0",
+ "@material/rtl": "^0.42.0",
+ "@material/shape": "^1.1.1",
+ "@material/theme": "^1.1.0",
+ "@material/typography": "^1.0.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/selection-control": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@material/selection-control/-/selection-control-0.41.0.tgz",
+ "integrity": "sha512-rRHGiZVPoP4nxAAoeqsgTsxz9GwInGs7HIlEhPfMFygmSZVUHHsuOJXSTpOKYi8GCoKHpB0RKZsAtxM0BYAelw==",
+ "requires": {
+ "@material/ripple": "^0.41.0"
+ },
+ "dependencies": {
+ "@material/animation": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@material/animation/-/animation-0.41.0.tgz",
+ "integrity": "sha512-yYAwJbX3Q2AFd4dr6IYOsWLQy2HN8zWOFVl9AbUXunjzTfJCa/ecfXCriaT6qkmoNoHeTdJHRrsQJZC5GsPvzA=="
+ },
+ "@material/base": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@material/base/-/base-0.41.0.tgz",
+ "integrity": "sha512-tEyzwBRu3d1H120SfKsDVYZHcqT5lKohh/7cWKR93aAaPDkSvjpKJIjyu2yuSkjpDduVZGzVocYbOvhUKhhzXQ=="
+ },
+ "@material/ripple": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-0.41.0.tgz",
+ "integrity": "sha512-rxEUVWM4AByDlTCH0kkthZQmUuY6eeN0X6cOHBoioFN2vUDk0D0Nfzz/N9FF2AlAf8C2lDDLrTuqnJPVIn+NHA==",
+ "requires": {
+ "@material/animation": "^0.41.0",
+ "@material/base": "^0.41.0",
+ "@material/theme": "^0.41.0"
+ }
+ },
+ "@material/theme": {
+ "version": "0.41.0",
+ "resolved": "https://registry.npmjs.org/@material/theme/-/theme-0.41.0.tgz",
+ "integrity": "sha512-ohW2JxObKOWvP34EkIIcrEVtL3g0Gs/T3/MdOsM36euyshY8Jwl1f6fjVUQvVjSpixUtSb30/+ulblF8fTOwBg=="
+ }
+ }
+ },
+ "@material/shape": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@material/shape/-/shape-1.1.1.tgz",
+ "integrity": "sha512-Jge/h1XBLjdLlam4QMSzVgM99e/N8+elQROPkltqVP7eyLc17BwM3aP5cLVfZDgrJgvsjUxbgAP1H1j8sqmUyg==",
+ "requires": {
+ "@material/feature-targeting": "^0.44.1"
+ }
+ },
+ "@material/textfield": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-2.3.1.tgz",
+ "integrity": "sha512-4w/HyJjNUTnYwuLpOaYEDN45bdFkBcZLC3QfCn3I0B3qrbM1zcpmDUxG0DYyGj1oXCM/sDJXMk07u6CcXpJU5w==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/dom": "^1.1.0",
+ "@material/floating-label": "^2.3.0",
+ "@material/line-ripple": "^2.3.0",
+ "@material/notched-outline": "^2.3.0",
+ "@material/ripple": "^2.3.0",
+ "@material/rtl": "^0.42.0",
+ "@material/shape": "^1.1.1",
+ "@material/theme": "^1.1.0",
+ "@material/typography": "^2.3.0",
+ "tslib": "^1.9.3"
+ },
+ "dependencies": {
+ "@material/floating-label": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-2.3.0.tgz",
+ "integrity": "sha512-OYcNmf+mVzW+rphDgVkyWES+SbivA6Y2+0amVDD9E9X6hLjO+L1dtIP3rC7lp0Y2Ey9rkuRORsUoriFvN7xQQw==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/rtl": "^0.42.0",
+ "@material/theme": "^1.1.0",
+ "@material/typography": "^2.3.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/line-ripple": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-2.3.0.tgz",
+ "integrity": "sha512-gDjUlrM6P182ldY4CYiBMcdcUtch1DWd1osgp5STJXxth6ukRCNIdDigrKCHtyJKB8eXeNORwWs39xWZOGihbA==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/notched-outline": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-2.3.0.tgz",
+ "integrity": "sha512-EDhpCGcMi1gW12qzXsdKHr6aYxa80s94tn9/G1r7eJu/BOVTYt20qjqoYQJE6i8XxcD1t5xcNdQ7StgGpk2MGA==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/floating-label": "^2.3.0",
+ "@material/rtl": "^0.42.0",
+ "@material/shape": "^1.1.1",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/ripple": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-2.3.0.tgz",
+ "integrity": "sha512-ejXR0nstERofFhssRyFlwOLgebwm2uGbarHtWZ2/+7QY2Th/Z1wOqNb2h/WRoShsJXK11RUsochb6BJrg30u7w==",
+ "requires": {
+ "@material/animation": "^1.0.0",
+ "@material/base": "^1.0.0",
+ "@material/dom": "^1.1.0",
+ "@material/feature-targeting": "^0.44.1",
+ "@material/theme": "^1.1.0",
+ "tslib": "^1.9.3"
+ }
+ },
+ "@material/typography": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@material/typography/-/typography-2.3.0.tgz",
+ "integrity": "sha512-NtWVVvwG9Te6/kuIl4fEwDcXGCS7mfPgo5CKPyxcK6y0hJHv6yRHpipJT9D4ZlXw0sQx9B33doOK7iYJtwBBZw==",
+ "requires": {
+ "@material/feature-targeting": "^0.44.1"
+ }
+ }
+ }
+ },
+ "@material/theme": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@material/theme/-/theme-1.1.0.tgz",
+ "integrity": "sha512-YYUV9Rhbx4r/EMb/zoOYJUWjhXChNaLlH1rqt3vpNVyxRcxGqoVMGp5u1XALBCFiD9dACPKLIkKyRYa928nmPQ==",
+ "requires": {
+ "@material/feature-targeting": "^0.44.1"
+ }
+ },
+ "@material/typography": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@material/typography/-/typography-1.0.0.tgz",
+ "integrity": "sha512-Oeqbjci1cC7jTE8/n3dwnkqKe9ZeWiaE+rgMtRYtRFw1HvAw14SpGA5EEAS/Li2Hu2KZ50FYCe3HYqShfxtChA==",
+ "requires": {
+ "@material/feature-targeting": "^0.44.1"
+ }
+ },
+ "@nx-js/observer-util": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@nx-js/observer-util/-/observer-util-4.2.2.tgz",
+ "integrity": "sha512-9OayX1xkdGjdnsDiO2YdaYJ6aMyCF7/NY4QWVgIgjSAZJ4OX2fD766Ts79hEzBscenQy2DCaSoY8VkguIMB1ZA=="
+ },
"@types/gh-pages": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/gh-pages/-/gh-pages-2.0.0.tgz",
@@ -51,6 +674,31 @@
"integrity": "sha512-7TEYTQT1/6PP53NftXXabIZDaZfaoBdeBm8Md/i7zsWRoBe0YwOXguyK8vhHs8ehgB/w9U4K/6EWuTyp0W6nIA==",
"dev": true
},
+ "@types/prop-types": {
+ "version": "15.7.1",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz",
+ "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==",
+ "dev": true
+ },
+ "@types/react": {
+ "version": "16.8.23",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.23.tgz",
+ "integrity": "sha512-abkEOIeljniUN9qB5onp++g0EY38h7atnDHxwKUFz1r3VH1+yG1OKi2sNPTyObL40goBmfKFpdii2lEzwLX1cA==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "csstype": "^2.2.0"
+ }
+ },
+ "@types/react-dom": {
+ "version": "16.8.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.4.tgz",
+ "integrity": "sha512-eIRpEW73DCzPIMaNBDP5pPIpK1KXyZwNgfxiVagb5iGiz6da+9A5hslSX6GAQKdO7SayVCS/Fr2kjqprgAvkfA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/tone": {
"version": "git+https://github.com/Tonejs/TypeScript.git#a8ea351b47459b3be26b14e13d6b67ba12e202f5",
"from": "git+https://github.com/Tonejs/TypeScript.git",
@@ -1076,6 +1724,11 @@
}
}
},
+ "classnames": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
+ "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+ },
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -1542,6 +2195,50 @@
"randomfill": "^1.0.3"
}
},
+ "css-loader": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.1.0.tgz",
+ "integrity": "sha512-MuL8WsF/KSrHCBCYaozBKlx+r7vIfUaDTEreo7wR7Vv3J6N0z6fqWjRk3e/6wjneitXN1r/Y9FTK1psYNOBdJQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.3.1",
+ "cssesc": "^3.0.0",
+ "icss-utils": "^4.1.1",
+ "loader-utils": "^1.2.3",
+ "normalize-path": "^3.0.0",
+ "postcss": "^7.0.17",
+ "postcss-modules-extract-imports": "^2.0.0",
+ "postcss-modules-local-by-default": "^3.0.2",
+ "postcss-modules-scope": "^2.1.0",
+ "postcss-modules-values": "^3.0.0",
+ "postcss-value-parser": "^4.0.0",
+ "schema-utils": "^2.0.0"
+ },
+ "dependencies": {
+ "schema-utils": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.0.1.tgz",
+ "integrity": "sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
+ "cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true
+ },
+ "csstype": {
+ "version": "2.6.6",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz",
+ "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==",
+ "dev": true
+ },
"cyclist": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
@@ -1683,11 +2380,6 @@
"integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
"dev": true
},
- "dialog-polyfill": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.5.0.tgz",
- "integrity": "sha512-fOj68T8KB6UIsDFmK7zYbmORJMLYkRmtsLM1W6wbCVUWu4Hdcud5bqbvuueTxO84JXtK9HcpCHV9vNwlWUdCIw=="
- },
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
@@ -2254,6 +2946,15 @@
"readable-stream": "^2.3.6"
}
},
+ "focus-trap": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-5.0.2.tgz",
+ "integrity": "sha512-jQf4ZkX9qOhiNrga4pRh9N2vMqYLRuGM7RZ6VRDLgti04cbG8m7XD4xlU/N0M0cqwDYtXbdzSBQDbRIwqn0FIg==",
+ "requires": {
+ "tabbable": "^4.0.0",
+ "xtend": "^4.0.1"
+ }
+ },
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -3193,6 +3894,15 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
+ "icss-utils": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
+ "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.14"
+ }
+ },
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
@@ -3221,6 +3931,12 @@
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
+ "indexes-of": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
+ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=",
+ "dev": true
+ },
"inflation": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz",
@@ -3446,8 +4162,7 @@
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.13.1",
@@ -3899,6 +4614,14 @@
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=",
"dev": true
},
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4143,6 +4866,11 @@
"p-is-promise": "^2.0.0"
}
},
+ "memoize-one": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.5.tgz",
+ "integrity": "sha512-ey6EpYv0tEaIbM/nTDOpHciXUvd+ackQrJgEzBwemhZZIWZjcyodqEcrmqDy2BKRTM3a65kKBV4WtLXJDt26SQ=="
+ },
"memory-fs": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
@@ -4465,8 +5193,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
- "dev": true
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-copy": {
"version": "0.1.0",
@@ -4777,6 +5504,92 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
"dev": true
},
+ "postcss": {
+ "version": "7.0.17",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz",
+ "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.2",
+ "source-map": "^0.6.1",
+ "supports-color": "^6.1.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "postcss-modules-extract-imports": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz",
+ "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.5"
+ }
+ },
+ "postcss-modules-local-by-default": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz",
+ "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==",
+ "dev": true,
+ "requires": {
+ "icss-utils": "^4.1.1",
+ "postcss": "^7.0.16",
+ "postcss-selector-parser": "^6.0.2",
+ "postcss-value-parser": "^4.0.0"
+ }
+ },
+ "postcss-modules-scope": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz",
+ "integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==",
+ "dev": true,
+ "requires": {
+ "postcss": "^7.0.6",
+ "postcss-selector-parser": "^6.0.0"
+ }
+ },
+ "postcss-modules-values": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz",
+ "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==",
+ "dev": true,
+ "requires": {
+ "icss-utils": "^4.0.0",
+ "postcss": "^7.0.6"
+ }
+ },
+ "postcss-selector-parser": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
+ "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
+ "dev": true,
+ "requires": {
+ "cssesc": "^3.0.0",
+ "indexes-of": "^1.0.1",
+ "uniq": "^1.0.1"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz",
+ "integrity": "sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==",
+ "dev": true
+ },
"prepend-http": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
@@ -4801,6 +5614,16 @@
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
"dev": true
},
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
"prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@@ -4919,6 +5742,41 @@
"unpipe": "1.0.0"
}
},
+ "react": {
+ "version": "16.8.6",
+ "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz",
+ "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2",
+ "scheduler": "^0.13.6"
+ }
+ },
+ "react-dom": {
+ "version": "16.8.6",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz",
+ "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "prop-types": "^15.6.2",
+ "scheduler": "^0.13.6"
+ }
+ },
+ "react-easy-state": {
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/react-easy-state/-/react-easy-state-6.1.3.tgz",
+ "integrity": "sha512-uWQ7ittvJylwn/Xgz7Ub1jjsbpthQ9Ad1KDLxXfbXCb2OPnov4porVdnOJU2PKeRezcam3ZgfPUtf9L9rjtyWg==",
+ "requires": {
+ "@nx-js/observer-util": "^4.2.2"
+ }
+ },
+ "react-is": {
+ "version": "16.8.6",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
+ "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
+ },
"read-pkg": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
@@ -5304,6 +6162,15 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
+ "scheduler": {
+ "version": "0.13.6",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
+ "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
"schema-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
@@ -5889,6 +6756,16 @@
"integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=",
"dev": true
},
+ "style-loader": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz",
+ "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "schema-utils": "^1.0.0"
+ }
+ },
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -5898,6 +6775,11 @@
"has-flag": "^3.0.0"
}
},
+ "tabbable": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz",
+ "integrity": "sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ=="
+ },
"table-layout": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.0.tgz",
@@ -6140,8 +7022,7 @@
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
- "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
- "dev": true
+ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
},
"tslint": {
"version": "5.18.0",
@@ -6240,6 +7121,12 @@
"set-value": "^2.0.1"
}
},
+ "uniq": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
+ "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
+ "dev": true
+ },
"unique-filename": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
@@ -6378,6 +7265,11 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
},
+ "utility-types": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.7.0.tgz",
+ "integrity": "sha512-mqRJXN7dEArK/NZNJUubjr9kbFFVZcmF/JHDc9jt5O/aYXUVmopHYujDMhLmLil1Bxo2+khe6KAIVvDH9Yc4VA=="
+ },
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
@@ -6679,8 +7571,7 @@
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
- "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
- "dev": true
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
},
"y18n": {
"version": "4.0.0",
diff --git a/package.json b/package.json
index ffe20ae..8562fc7 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,6 @@
"main": "out/index.js",
"types": "out/index.d.ts",
"scripts": {
- "assets": "ts-node tasks/assets.ts",
"compile": "tsc -p ./",
"deploy": "ts-node tasks/deploy.ts",
"dev": "concurrently npm:watch npm:webpack-dev npm:serve",
@@ -31,19 +30,30 @@
"/out"
],
"dependencies": {
- "dialog-polyfill": "^0.5.0",
+ "@material/react-button": "^0.14.1",
+ "@material/react-dialog": "^0.15.0",
+ "@material/react-menu-surface": "^0.15.0",
+ "@material/react-select": "^0.14.1",
+ "@material/react-text-field": "^0.14.1",
+ "react": "^16.8.6",
+ "react-dom": "^16.8.6",
+ "react-easy-state": "^6.1.3",
"tone": "^13.4.9"
},
"devDependencies": {
"@types/gh-pages": "^2.0.0",
"@types/js-yaml": "^3.12.1",
"@types/node": "^12.6.3",
+ "@types/react": "^16.8.23",
+ "@types/react-dom": "^16.8.4",
"@types/tone": "git+https://github.com/Tonejs/TypeScript.git",
"concurrently": "^4.1.1",
+ "css-loader": "^3.1.0",
"gh-pages": "^2.0.1",
"js-yaml": "^3.13.1",
"js-yaml-loader": "^1.2.2",
"local-web-server": "^3.0.4",
+ "style-loader": "^0.23.1",
"ts-loader": "^6.0.4",
"ts-node": "^8.3.0",
"tslint": "^5.18.0",
diff --git a/index.css b/public/index.css
similarity index 81%
rename from index.css
rename to public/index.css
index de0a248..90cf9c0 100644
--- a/index.css
+++ b/public/index.css
@@ -10,6 +10,19 @@ body {
width: 100%;
}
+.mdc-text-field {
+ margin-left: 10px;
+}
+
+.effect-panel {
+ display: inline;
+}
+
+.effect-panel-popup {
+ display: flex;
+ flex-flow: column;
+}
+
#app {
display: flex;
flex-flow: column;
@@ -17,12 +30,12 @@ body {
width: 100%;
}
-#playButton, #effectsButton, #helpButton {
+button {
margin-left: 10px;
margin-right: 10px;
}
-#playButton, #helpButton {
+#playButton {
width: 100px;
}
@@ -84,7 +97,7 @@ table tr:nth-of-type(12n) {
border-bottom: 3px solid white;
}
-.metaWorkspace {
+#metaWorkspace {
background-color: transparent;
margin-bottom: 10px;
}
@@ -93,19 +106,14 @@ table tr:nth-of-type(12n) {
flex-grow: 1;
overflow-y: auto;
width: 100%;
- user-select: none;
}
table#pattern {
width: 100%;
}
-/* MDL hides these by default. */
-
-input[type=number]::-webkit-outer-spin-button, input[type=number]::-webkit-inner-spin-button {
- -webkit-appearance: inner-spin-button !important;
-}
-
-#helpModal {
- width: 600px;
+.no-select {
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
}
diff --git a/src/audio.ts b/src/audio.ts
index ef2faa0..1ca2cfb 100644
--- a/src/audio.ts
+++ b/src/audio.ts
@@ -1,4 +1,4 @@
-import * as tone from "tone";
+import tone from "tone";
import { assertNever, notes } from "./index";
import { instrumentData } from "./data";
diff --git a/src/player.ts b/src/player.ts
deleted file mode 100644
index a95888b..0000000
--- a/src/player.ts
+++ /dev/null
@@ -1,348 +0,0 @@
-import * as tone from 'tone';
-import { changeSampler, getSampler } from "./audio";
-const dialogPolyfill = require("dialog-polyfill");
-
-let playing = false;
-const letters = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
-const chords = [1, 2, 3, 4, 5, 6, 7, 8, 9];
-
-const volume = new tone.Volume(0);
-const lowPass = new tone.LowpassCombFilter(0, 0);
-
-const delayEffect = new tone.FeedbackDelay(0, 0);
-const chorusEffect = new tone.Chorus();
-chorusEffect.wet.value = 0;
-const reverbEffect = new tone.Freeverb(0, 3000);
-reverbEffect.wet.value = 0;
-const distortionEffect = new tone.BitCrusher(4);
-distortionEffect.wet.value = 0;
-const lowBoostEffect = new tone.Filter(0, "lowshelf");
-const compressorEffect = new tone.Compressor(0);
-const highPassEffect = new tone.Filter(0, "highpass");
-
-interface Note {
- length: number;
- scheduledEvent: number | null;
-}
-
-let instrumentName = "midi.piano1";
-let sampler = getSampler("midi.piano1");
-let patterns: { [key: number]: { [key: string]: Array } } = {};
-for (const chord of chords) {
- patterns[chord] = {};
- for (const letter of letters) {
- patterns[chord][letter] = Array.from({ length: 16 }, () => { return { length: 0, scheduledEvent: null }; });
- }
-}
-
-function toggleAudio() {
- if (playing) {
- tone.Transport.stop();
- tone.Transport.position = "0";
- } else {
- resumeAudioContext();
- tone.Transport.loopEnd = '1m';
- tone.Transport.loop = true;
- }
- tone.Transport.toggle(0);
- playing = !playing;
-}
-
-function resumeAudioContext() {
- let ac = (tone as any).context;
- if (ac.state !== "running") {
- ac.resume();
- }
-}
-
-// mdl-selectfield doesn't play nice with custom onchange event listeners,
-// so we use this to handle instrument changes instead.
-function setInstrumentLoop(instrumentField: HTMLSelectElement) {
- setInstrument(instrumentField);
- setTimeout(() => { setInstrumentLoop(instrumentField); }, 250);
-}
-
-function setInstrument(instrumentField: HTMLSelectElement) {
- if (instrumentName !== instrumentField.value) {
- instrumentName = instrumentField.value;
- changeSampler(sampler, instrumentField.value);
- }
-}
-
-function setBpm() {
- let bpmField = document.getElementById("bpm");
- if (bpmField !== null && bpmField instanceof HTMLInputElement) {
- tone.Transport.bpm.value = parseInt(bpmField.value);
- }
-}
-
-function setVolume() {
- const field = document.getElementById("volume");
- if (field !== null && field instanceof HTMLInputElement) {
- let newValue = parseInt(field.value);
- if (newValue === 0) {
- volume.mute = true;
- } else {
- volume.volume.value = (newValue - 100) / 5;
- volume.mute = false;
- }
- }
-}
-
-function setSwing() {
- const field = document.getElementById("swing");
- if (field !== null && field instanceof HTMLInputElement) {
- tone.Transport.swing = parseFloat(field.value);
- tone.Transport.swingSubdivision = "16n";
- }
-}
-
-function setResonance() {
- const field = document.getElementById("resonance");
- if (field !== null && field instanceof HTMLInputElement) {
- lowPass.resonance.value = parseFloat(field.value);
- }
-}
-
-function setDampening() {
- const field = document.getElementById("dampening");
- if (field !== null && field instanceof HTMLInputElement) {
- lowPass.dampening.value = parseInt(field.value);
- }
-}
-
-function togglePlayButton() {
- let playButton = document.getElementById("playButton");
- if (playButton !== null) {
- if (playButton.textContent !== null && playButton.textContent.trim().toLowerCase() === "play") {
- playButton.textContent = "Stop";
- } else {
- playButton.textContent = "Play";
- }
- }
-}
-
-function setEffect(effect: string, value: number) {
- switch (effect) {
- case "delay":
- delayEffect.delayTime.value = value === 0 ? 0 : tone.Time("8n") * 2 * value;
- delayEffect.feedback.value = 0.15 * 2 * value;
- break;
- case "chorus":
- chorusEffect.wet.value = value;
- break;
- case "reverb":
- reverbEffect.roomSize.value = value * 0.9;
- reverbEffect.wet.value = value;
- break;
- case "distortion":
- distortionEffect.wet.value = value === 0 ? 0 : 1;
- distortionEffect.bits = value;
- break;
- case "lowBoost":
- lowBoostEffect.frequency.value = value;
- lowBoostEffect.gain.value = value === 0 ? 0 : 20;
- break;
- case "compressor":
- compressorEffect.threshold.value = value;
- break;
- case "highPass":
- highPassEffect.frequency.value = value;
- break;
- }
-}
-
-function scheduleNote(chord: number, letter: string, index: number, length: number) {
- unscheduleNote(chord, letter, index);
- // console.log(`scheduleNote(chord=${chord}, letter=${letter}, index=${index}, length=${length})`);
- patterns[chord][letter][index]["length"] = length;
-
- patterns[chord][letter][index]["scheduledEvent"] = tone.Transport.schedule(
- time => {
- sampler.triggerAttackRelease(`${letter}${chord}`, tone.Time("16n") * length, time);
- tone.Draw.schedule(() => {
- const noteElement = document.querySelector(`#${letter.replace("#", "\\#")}-${chord}-${index}`);
- if (noteElement !== null) {
- if (length <= 1) {
- noteElement.classList.add("playing");
- } else {
- noteElement.classList.add("playingLong");
- }
- setTimeout(() => {
- noteElement.classList.remove("playing");
- noteElement.classList.remove("playingLong");
- }, 100 * length);
- }
- }, time);
- },
- `0:0:${index}`
- );
-}
-
-function unscheduleNote(chord: number, letter: string, index: number) {
- patterns[chord][letter][index]["length"] = 0;
- const schedulee = patterns[chord][letter][index]["scheduledEvent"];
- if (schedulee !== null) {
- tone.Transport.clear(schedulee);
- patterns[chord][letter][index]["scheduledEvent"] = null;
- }
-}
-
-function onClickNoteCell(event: MouseEvent, cell: HTMLTableCellElement, chord: number, letter: string, index: number) {
- let length = patterns[chord][letter][index]["length"];
- // console.log(`onClickNoteCell(chord=${chord}, letter=${letter}, index=${index}) | length ${length}`);
-
- if (event.shiftKey && event.ctrlKey) {
- length = Math.max(length - 1, 0);
- } else if (event.shiftKey) {
- length = Math.max(Math.min(length + 1, 16), 2);
- } else if (length > 0) {
- length = 0;
- } else {
- length = 1;
- }
-
- cell.innerHTML = length.toString();
-
- if (length <= 1) {
- cell.classList.remove("noteLong");
- } else {
- cell.classList.add("noteLong");
- }
-
- if (length > 0 && length !== patterns[chord][letter][index]["length"]) {
- cell.classList.add("active");
- scheduleNote(chord, letter, index, length);
- } else if (length === 0) {
- cell.classList.remove("active");
- unscheduleNote(chord, letter, index);
- }
-}
-
-function onLoad() {
- sampler.chain(
- volume,
- lowPass,
- delayEffect,
- chorusEffect,
- reverbEffect,
- distortionEffect,
- lowBoostEffect,
- compressorEffect,
- highPassEffect,
- tone.Master
- );
-
- document.onkeypress = event => {
- if (event.keyCode === 32) {
- toggleAudio();
- togglePlayButton();
- return false;
- }
- };
-
- let playButton = document.getElementById("playButton");
- if (playButton !== null) {
- playButton.addEventListener("click", toggleAudio);
- playButton.addEventListener("click", togglePlayButton);
- }
-
- let helpButton = document.getElementById("helpButton");
- let helpModal = document.getElementById("helpModal");
- if (helpButton !== null && helpModal !== null) {
- if (!(helpModal as any).showModal) {
- dialogPolyfill.default.registerDialog(helpModal);
- }
- helpButton.addEventListener("click", () => { (helpModal as any).showModal(); });
- const helpModalClose = helpModal.querySelector(".close");
- if (helpModalClose !== null) {
- helpModalClose.addEventListener("click", () => {
- (helpModal as any).close();
- });
- }
- }
-
- let instrumentField = document.getElementById("instruments");
- if (instrumentField !== null && instrumentField instanceof HTMLSelectElement) {
- setInstrumentLoop(instrumentField);
- }
-
- let bpmField = document.getElementById("bpm");
- if (bpmField !== null) {
- bpmField.addEventListener("change", setBpm);
- }
-
- let effectsMenu = document.getElementById("effectsMenu");
- if (effectsMenu !== null) {
- effectsMenu.addEventListener("click", event => {
- event.stopPropagation();
- });
- }
-
- const effects = ["delay", "chorus", "reverb", "distortion", "lowBoost", "compressor", "highPass"];
- for (const effect of effects) {
- const effectField = document.getElementById(`${effect}Effect`);
- if (effectField !== null && effectField instanceof HTMLInputElement) {
- effectField.addEventListener("change", () => {
- setEffect(effect, parseFloat(effectField.value));
- });
- }
- }
-
- let patternTable = document.getElementById("pattern");
- if (patternTable !== null && patternTable instanceof HTMLTableElement) {
- for (const chord of chords.slice().reverse()) {
- let first = true;
-
-
- for (const letter of letters.slice().reverse()) {
- let row = patternTable.insertRow();
- if (first) {
- first = false;
- let chordHeader = document.createElement("th");
- chordHeader.rowSpan = letters.length;
- chordHeader.innerHTML = chord.toString();
- row.appendChild(chordHeader);
- }
-
- let letterHeader = document.createElement("th");
- letterHeader.innerHTML = letter;
- row.appendChild(letterHeader);
-
- for (const i of Array(16).keys()) {
- let cell = row.insertCell();
- cell.id = `${letter}-${chord}-${i}`;
- cell.classList.add(`note-${letter}`);
- cell.onclick = event => { onClickNoteCell(event, cell, chord, letter, i); };
- }
- }
- }
- }
-
- const centralRow = document.getElementById("F-5-0");
- if (centralRow !== null) {
- centralRow.scrollIntoView({ "behavior": "smooth", "block": "center" });
- }
-
- let volumeField = document.getElementById("volume");
- if (volumeField !== null) {
- volumeField.addEventListener("change", setVolume);
- }
-
- let swingField = document.getElementById("swing");
- if (swingField !== null) {
- swingField.addEventListener("change", setSwing);
- }
-
- let resonanceField = document.getElementById("resonance");
- if (resonanceField !== null) {
- resonanceField.addEventListener("change", setResonance);
- }
-
- let dampeningField = document.getElementById("dampening");
- if (dampeningField !== null) {
- dampeningField.addEventListener("change", setDampening);
- }
-}
-
-window.onload = onLoad;
diff --git a/src/player.tsx b/src/player.tsx
new file mode 100644
index 0000000..97de006
--- /dev/null
+++ b/src/player.tsx
@@ -0,0 +1,675 @@
+require("@material/react-button/dist/button.css");
+require("@material/react-select/dist/select.css");
+require("@material/react-text-field/dist/text-field.css");
+require("@material/react-menu-surface/dist/menu-surface.css");
+require("@material/react-dialog/dist/dialog.css");
+
+import { store, view } from "react-easy-state";
+import tone from "tone";
+
+import React from "react";
+import ReactDom from "react-dom";
+import Button from "@material/react-button";
+import Select, { Option } from "@material/react-select";
+import TextField, { Input } from "@material/react-text-field";
+import MenuSurface, { Corner } from "@material/react-menu-surface";
+import Dialog, { DialogTitle, DialogContent, DialogFooter, DialogButton } from "@material/react-dialog";
+
+import { changeSampler, getSampler } from "./audio";
+import { instrumentData } from "./data";
+import { assertNever } from "./index";
+
+let playing = false;
+const letters = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
+const chords = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+
+function fromMaybes(maybes: Array, fallback: T) {
+ for (const value of maybes) {
+ if (value !== null && value !== undefined) {
+ return value;
+ }
+ }
+ return fallback;
+}
+
+class Song {
+ meta: SongMeta = new SongMeta();
+ effects: Effects = new Effects();
+ patterns: Array = [new Pattern()];
+ channels: Array = [new Channel()];
+ playing: boolean = false;
+
+ toggleAudio(): void {
+ if (this.playing) {
+ tone.Transport.stop();
+ tone.Transport.position = "0";
+ } else {
+ resumeAudioContext();
+ tone.Transport.loopEnd = "1m";
+ tone.Transport.loop = true;
+ }
+ tone.Transport.toggle(0);
+ this.playing = !this.playing;
+ }
+
+ getEffect(effect: keyof Effects, channel: number, pattern: number): number {
+ return fromMaybes(
+ [
+ this.patterns[pattern].effects[effect],
+ this.channels[channel].effects[effect],
+ ],
+ this.effects[effect]
+ );
+ }
+
+ getEffectBy(effect: keyof Effects, level: "song" | "channel" | "pattern", index: number = 0) {
+ switch (level) {
+ case "song":
+ return this.effects[effect];
+ case "channel":
+ return this.channels[index].effects[effect];
+ case "pattern":
+ return this.patterns[index].effects[effect];
+ }
+ }
+
+ setEffectBy(effect: keyof Effects, value: number | undefined, level: "song" | "channel" | "pattern", index: number = 0) {
+ switch (level) {
+ case "song":
+ if (value === undefined) {
+ throw new Error("Song-level effects cannot be undefined");
+ }
+ this.effects[effect] = value;
+ for (let channel = 0; channel < this.channels.length; channel++) {
+ for (const pattern of this.channels[channel].patterns) {
+ if (pattern !== null) {
+ this.patterns[pattern].pipeline.setEffect(effect, this.getEffect(effect, channel, pattern));
+ }
+ }
+ }
+ break;
+ case "channel":
+ this.channels[index].effects[effect] = value;
+ for (const pattern of this.channels[index].patterns) {
+ if (pattern !== null) {
+ this.patterns[pattern].pipeline.setEffect(effect, this.getEffect(effect, index, pattern));
+ }
+ }
+ break;
+ case "pattern":
+ this.patterns[index].effects[effect] = value;
+ for (let channel = 0; channel < this.channels.length; channel++) {
+ for (const pattern of this.channels[channel].patterns) {
+ if (pattern === index) {
+ this.patterns[pattern].pipeline.setEffect(effect, this.getEffect(effect, channel, index));
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ setInstrument(value: string, pattern: number): void {
+ this.patterns[pattern].instrument = value;
+ changeSampler(this.patterns[pattern].pipeline.sampler, value);
+ }
+
+ setBpm(value: number): void {
+ this.meta.bpm = value;
+ tone.Transport.bpm.value = song.meta.bpm;
+ }
+
+ setSwing(value: number): void {
+ this.meta.swing = value;
+ tone.Transport.swing = song.meta.swing;
+ tone.Transport.swingSubdivision = "16n";
+ }
+
+ scheduleNote(channel: number, pattern: number, chord: number, letter: string, index: number, length: number, noteCell: NoteCell) {
+ this.unscheduleNote(channel, pattern, chord, letter, index);
+ // console.log(`Song.scheduleNote(chord=${chord}, letter=${letter}, index=${index}, length=${length})`);
+ this.patterns[pattern].notes[chord][letter][index].length = length;
+
+ this.patterns[pattern].notes[chord][letter][index].scheduledEvent = tone.Transport.schedule(
+ time => {
+ this.patterns[pattern].pipeline.sampler.triggerAttackRelease(
+ `${letter}${chord}`, tone.Time("16n") * length, time
+ );
+ tone.Draw.schedule(() => {
+ noteCell.setState({ playing: true });
+ setTimeout(() => {
+ noteCell.setState({ playing: false });
+ }, 100 * length);
+ }, time);
+ },
+ `0:0:${index}`
+ );
+ }
+
+ unscheduleNote(channel: number, pattern: number, chord: number, letter: string, index: number) {
+ // console.log(`Song.unscheduleNote(chord=${chord}, letter=${letter}, index=${index}, length=${length})`);
+ this.patterns[pattern].notes[chord][letter][index].length = 0;
+ const schedulee = this.patterns[pattern].notes[chord][letter][index].scheduledEvent;
+ if (schedulee !== null) {
+ tone.Transport.clear(schedulee);
+ this.patterns[pattern].notes[chord][letter][index].scheduledEvent = null;
+ }
+ }
+}
+
+class SongMeta {
+ bpm: number = 120;
+ swing: number = 0;
+}
+
+class Effects {
+ volume: number = 100;
+ resonance: number = 0;
+ dampening: number = 0;
+ delay: number = 0;
+ chorus: number = 0;
+ reverb: number = 0;
+ distortion: number = 0;
+ lowBoost: number = 0;
+ compressor: number = 0;
+ highPass: number = 0;
+}
+
+class Pipeline {
+ sampler: tone.Sampler;
+ volume = new tone.Volume(0);
+ lowPass = new tone.LowpassCombFilter(0, 0);
+ delayEffect = new tone.FeedbackDelay(0, 0);
+ chorusEffect = new tone.Chorus();
+ reverbEffect = new tone.Freeverb(0, 3000);
+ distortionEffect = new tone.BitCrusher(4);
+ lowBoostEffect = new tone.Filter(0, "lowshelf");
+ compressorEffect = new tone.Compressor(0);
+ highPassEffect = new tone.Filter(0, "highpass");
+
+ constructor(instrument: string = "midi.piano1") {
+ this.sampler = getSampler(instrument);
+ this.chorusEffect.wet.value = 0;
+ this.reverbEffect.wet.value = 0;
+ this.distortionEffect.wet.value = 0;
+
+ this.sampler.chain(
+ this.volume,
+ this.lowPass,
+ this.delayEffect,
+ this.chorusEffect,
+ this.reverbEffect,
+ this.distortionEffect,
+ this.lowBoostEffect,
+ this.compressorEffect,
+ this.highPassEffect,
+ tone.Master,
+ );
+ }
+
+ setEffect(effect: keyof Effects, value: number): void {
+ switch (effect) {
+ case "volume":
+ if (value === 0) {
+ this.volume.mute = true;
+ } else {
+ this.volume.volume.value = (value - 100) / 5;
+ this.volume.mute = false;
+ }
+ break;
+ case "resonance":
+ this.lowPass.resonance.value = value;
+ break;
+ case "dampening":
+ this.lowPass.dampening.value = value;
+ break;
+ case "delay":
+ this.delayEffect.delayTime.value = value === 0 ? 0 : tone.Time("8n") * 2 * value;
+ this.delayEffect.feedback.value = 0.15 * 2 * value;
+ break;
+ case "chorus":
+ this.chorusEffect.wet.value = value;
+ break;
+ case "reverb":
+ this.reverbEffect.roomSize.value = value * 0.9;
+ this.reverbEffect.wet.value = value;
+ break;
+ case "distortion":
+ this.distortionEffect.wet.value = value === 0 ? 0 : 1;
+ this.distortionEffect.bits = value;
+ break;
+ case "lowBoost":
+ this.lowBoostEffect.frequency.value = value;
+ this.lowBoostEffect.gain.value = value === 0 ? 0 : 20;
+ break;
+ case "compressor":
+ this.compressorEffect.threshold.value = value;
+ break;
+ case "highPass":
+ this.highPassEffect.frequency.value = value;
+ break;
+ default:
+ assertNever(effect);
+ }
+ }
+}
+
+class Pattern {
+ effects: Partial = {};
+ notes: { [key: number]: { [key: string]: Array } } = {};
+ pipeline: Pipeline;
+
+ constructor(public instrument: string = "midi.piano1") {
+ this.pipeline = new Pipeline(this.instrument);
+ for (const chord of chords) {
+ this.notes[chord] = {};
+ for (const letter of letters) {
+ this.notes[chord][letter] = Array.from({ length: 16 }, () => ({ length: 0, scheduledEvent: null }));
+ }
+ }
+ }
+}
+
+class Channel {
+ effects: Partial = {};
+ patterns: Array;
+
+ constructor(patterns: Array = [0]) {
+ this.patterns = patterns;
+ }
+}
+
+const song: Song = store(new Song());
+
+@view
+class EffectChoice extends React.Component<{
+ effect: keyof Effects,
+ mode: "song" | "channel" | "pattern",
+ modeIndex: number,
+ min: number,
+ max: number,
+ step: number,
+}> {
+ getLabel(): string {
+ switch (this.props.effect) {
+ case "chorus":
+ return "Chorus";
+ case "compressor":
+ return "Compressor";
+ case "dampening":
+ return "Dampening";
+ case "delay":
+ return "Delay";
+ case "distortion":
+ return "Distortion";
+ case "highPass":
+ return "High pass";
+ case "lowBoost":
+ return "Low boost";
+ case "resonance":
+ return "Resonance";
+ case "reverb":
+ return "Reverb";
+ case "volume":
+ return "Volume";
+ default:
+ return assertNever(this.props.effect);
+ }
+ }
+ render(): React.ReactNode {
+ return
+ {
+ if (this.props.mode !== "song" && e.currentTarget.value === "") {
+ song.setEffectBy(this.props.effect, undefined, this.props.mode, this.props.modeIndex);
+ return;
+ }
+ const newValue = parseFloat(e.currentTarget.value);
+ if (!isNaN(newValue)) {
+ song.setEffectBy(this.props.effect, newValue, this.props.mode, this.props.modeIndex);
+ } else if (e.currentTarget.value === undefined && this.props.mode !== "song") {
+ song.setEffectBy(this.props.effect, undefined, this.props.mode, this.props.modeIndex);
+ }
+ }}
+ />
+ ;
+ }
+}
+
+@view
+class EffectPanel extends React.Component<{
+ mode: "song" | "channel" | "pattern",
+ modeIndex: number,
+}> {
+ state = {
+ open: false,
+ anchorElement: undefined,
+ };
+
+ getButtonText(): string {
+ switch (this.props.mode) {
+ case "song":
+ return "Song";
+ case "channel":
+ return "Channel";
+ case "pattern":
+ return "Pattern"
+ default:
+ return assertNever(this.props.mode);
+ }
+ }
+
+ setAnchorElement = (element: HTMLDivElement) => {
+ if (this.state.anchorElement) {
+ return;
+ }
+ this.setState({ anchorElement: element });
+ }
+
+ render() {
+ return (
+
+
this.setState({ open: true })}>{this.getButtonText()}
+
+
this.setState({ open: false })}
+ anchorElement={this.state.anchorElement}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+@view
+class PlayButton extends React.Component {
+ onClick() {
+ song.toggleAudio();
+ }
+ render(): React.ReactNode {
+ return this.onClick()} raised>
+ {song.playing ? "Stop" : "Play"}
+ ;
+ }
+}
+
+@view
+class Help extends React.Component {
+ state = {
+ displayed: false,
+ };
+ render(): React.ReactNode {
+ return
+ this.setState({displayed: true})} raised>Help
+ this.setState({displayed: false})}>
+ Help
+
+
+
+ Click in a box to add or remove a note.
+ Shift click to extend a note, and ctrl-shift click to shorten it.
+ Press the space bar to play or stop the song.
+ After clicking in an entry field, use the arrow keys to quickly change the value.
+
+
+
+
+ Close
+
+
+ ;
+ }
+}
+
+@view
+class MetaWorkspace extends React.Component<{ activeChannel: number, activePattern: number }> {
+ render(): React.ReactNode {
+ const instruments = [];
+ for (const [instrument, { category }] of Object.entries(instrumentData)) {
+ instruments.push(
+ {category.join(" > ")}
+ );
+ }
+
+ return ;
+ }
+}
+
+@view
+class NoteCell extends React.Component<{ channel: number, pattern: number, chord: number, letter: string, index: number }> {
+ state = {
+ chord: this.props.chord,
+ letter: this.props.letter,
+ index: this.props.index,
+ length: 0,
+ playing: false,
+ };
+
+ onClick(chord: number, letter: string, index: number, withShift: boolean, withCtrl: boolean) {
+ let length = this.state.length;
+
+ if (withShift && withCtrl) {
+ length = Math.max(length - 1, 0);
+ } else if (withShift) {
+ length = Math.max(Math.min(length + 1, 16), 2);
+ } else if (length > 0) {
+ length = 0;
+ } else {
+ length = 1;
+ }
+
+ if (length > 0 && length !== this.state.length) {
+ song.scheduleNote(this.props.channel, this.props.pattern, chord, letter, index, length, this);
+ } else if (length === 0) {
+ song.unscheduleNote(this.props.channel, this.props.pattern, chord, letter, index);
+ }
+
+ this.setState({ length: length });
+ }
+
+ render(): React.ReactNode {
+ const classes = [`note-${this.state.letter}`];
+ if (this.state.length > 0) {
+ classes.push("active");
+ }
+ if (this.state.length > 1) {
+ classes.push("noteLong");
+ }
+ if (this.state.playing) {
+ if (this.state.length > 1) {
+ classes.push("playingLong");
+ } else {
+ classes.push("playing");
+ }
+ }
+
+ return {
+ this.onClick(this.state.chord, this.state.letter, this.state.index, event.shiftKey, event.ctrlKey);
+ }}
+ >
+ {this.state.length === 0 ? "" : this.state.length}
+ ;
+ }
+}
+
+@view
+class PatternWorkspace extends React.Component<{ channel: number, pattern: number }> {
+ render(): React.ReactNode {
+ let rows = [];
+ for (const chord of chords.slice().reverse()) {
+ let first = true;
+ for (const letter of letters.slice().reverse()) {
+ let items = [];
+ if (first) {
+ first = false;
+ items.push({chord} );
+ }
+
+ items.push({letter} );
+
+ for (const i of Array(16).keys()) {
+ items.push(
+
+ );
+ }
+ rows.push({items} );
+ }
+ }
+
+ return ;
+ }
+}
+
+@view
+class App extends React.Component {
+ state = {
+ activeChannel: 0,
+ activePattern: 0,
+ };
+ samplers: { [key: number]: tone.Sampler } = {};
+ render(): React.ReactNode {
+ return ;
+ }
+}
+
+interface Note {
+ length: number;
+ scheduledEvent: number | null;
+}
+
+let patterns: { [key: number]: { [key: string]: Array } } = {};
+for (const chord of chords) {
+ patterns[chord] = {};
+ for (const letter of letters) {
+ patterns[chord][letter] = Array.from({ length: 16 }, () => { return { length: 0, scheduledEvent: null }; });
+ }
+}
+
+function toggleAudio() {
+ if (playing) {
+ tone.Transport.stop();
+ tone.Transport.position = "0";
+ } else {
+ resumeAudioContext();
+ tone.Transport.loopEnd = "1m";
+ tone.Transport.loop = true;
+ }
+ tone.Transport.toggle(0);
+ playing = !playing;
+}
+
+function resumeAudioContext() {
+ let ac = (tone as any).context;
+ if (ac.state !== "running") {
+ ac.resume();
+ }
+}
+
+function onLoad() {
+ ReactDom.render( , document.querySelector("#root"));
+
+ document.onkeypress = event => {
+ if (event.keyCode === 32) {
+ song.toggleAudio();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ };
+
+ const centralRow = document.getElementById("F-5-0");
+ if (centralRow !== null) {
+ centralRow.scrollIntoView({ "behavior": "smooth", "block": "center" });
+ }
+}
+
+window.onload = onLoad;
diff --git a/tasks/assets.ts b/tasks/assets.ts
deleted file mode 100644
index e88f3e2..0000000
--- a/tasks/assets.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as http from "http";
-import * as https from "https";
-import * as fs from "fs";
-import * as path from "path";
-
-const root = `${path.dirname(process.argv[1])}/..`;
-const assets = `${root}/assets`;
-
-function download(filename: string, url: string): void {
- var file = fs.createWriteStream(filename);
- var protocol = url.startsWith("https://") ? https : http;
- protocol.get(url, response => {
- response.pipe(file);
- });
-}
-
-if (!fs.existsSync(assets)) {
- fs.mkdirSync(assets);
-}
-download(
- `${assets}/md-icons.css`,
- "https://fonts.googleapis.com/icon?family=Material+Icons"
-);
-download(
- `${assets}/material.indigo-pink.min.css`,
- "https://code.getmdl.io/1.3.0/material.indigo-pink.min.css"
-);
-download(
- `${assets}/material.min.js`,
- "https://code.getmdl.io/1.3.0/material.min.js"
-);
-download(
- `${assets}/mdl-selectfield.min.css`,
- "https://cdn.rawgit.com/kybarg/mdl-selectfield/mdl-menu-implementation/mdl-selectfield.min.css"
-);
-download(
- `${assets}/mdl-selectfield.min.js`,
- "https://cdn.rawgit.com/kybarg/mdl-selectfield/mdl-menu-implementation/mdl-selectfield.min.js"
-);
-download(
- `${assets}/dialog-polyfill.min.css`,
- "https://cdnjs.cloudflare.com/ajax/libs/dialog-polyfill/0.5.0/dialog-polyfill.min.css"
-);
diff --git a/tasks/deploy.ts b/tasks/deploy.ts
index 023ec26..83b9dbb 100644
--- a/tasks/deploy.ts
+++ b/tasks/deploy.ts
@@ -1,5 +1,5 @@
-import * as ghPages from "gh-pages";
-import * as path from "path";
+import ghPages from "gh-pages";
+import path from "path";
const root = `${path.dirname(process.argv[1])}/..`;
@@ -8,9 +8,8 @@ ghPages.publish(
{
"src": [
"index.html",
- "index.css",
- "assets/*.css",
- "assets/*.js",
+ "public/*.css",
+ "public/*.js",
"audio/**/*.ogg",
]
},
diff --git a/tasks/samples.ts b/tasks/samples.ts
index aaa0064..e4167ab 100644
--- a/tasks/samples.ts
+++ b/tasks/samples.ts
@@ -1,8 +1,8 @@
-import * as fs from "fs";
-import * as path from "path";
-import * as process from "process";
-import * as child_process from "child_process";
-import * as yaml from "js-yaml";
+import fs from "fs";
+import path from "path";
+import process from "process";
+import child_process from "child_process";
+import yaml from "js-yaml";
import { InstrumentData, notes } from "../src/index";
if (process.argv.length < 3) {
diff --git a/tsconfig.json b/tsconfig.json
index 35df224..49b83b1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,6 +12,10 @@
"noFallthroughCasesInSwitch": true,
"noUnusedParameters": true,
"strictNullChecks": true,
+ "jsx": "react",
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "experimentalDecorators": true,
},
"exclude": [
"node_modules",
diff --git a/webpack.config.js b/webpack.config.js
index a1dd5df..fe56c20 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -2,7 +2,7 @@ path = require("path");
module.exports = {
entry: {
- index: './src/player.ts'
+ index: './src/player.tsx'
},
module: {
rules: [
@@ -11,6 +11,10 @@ module.exports = {
use: 'ts-loader',
exclude: /node_modules/
},
+ {
+ test: /\.css$/,
+ use: ["style-loader", "css-loader"],
+ },
{
test: /\.yaml$/,
use: 'js-yaml-loader',
@@ -23,6 +27,6 @@ module.exports = {
devtool: 'inline-source-map',
output: {
filename: '[name].js',
- path: path.resolve(__dirname, 'assets')
+ path: path.resolve(__dirname, 'public')
}
};