From 716982f579c75976ba9033a6129ad9b83296b826 Mon Sep 17 00:00:00 2001 From: mtkennerly Date: Sat, 27 Jul 2019 20:03:45 -0400 Subject: [PATCH] Integrate React and prepare state management for multiple channels and patterns --- .gitignore | 1 + README.md | 1 - index.html | 161 +----- package-lock.json | 917 +++++++++++++++++++++++++++++++++- package.json | 14 +- index.css => public/index.css | 32 +- src/audio.ts | 2 +- src/player.ts | 348 ------------- src/player.tsx | 675 +++++++++++++++++++++++++ tasks/assets.ts | 43 -- tasks/deploy.ts | 9 +- tasks/samples.ts | 10 +- tsconfig.json | 4 + webpack.config.js | 8 +- 14 files changed, 1635 insertions(+), 590 deletions(-) rename index.css => public/index.css (81%) delete mode 100644 src/player.ts create mode 100644 src/player.tsx delete mode 100644 tasks/assets.ts 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.
  • -
-
-
- -
-
+
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: false })} + anchorElement={this.state.anchorElement} + > +
+ + + + + + + + + + +
+
+
+ ); + } +} + +@view +class PlayButton extends React.Component { + onClick() { + song.toggleAudio(); + } + render(): React.ReactNode { + return ; + } +} + +@view +class Help extends React.Component { + state = { + displayed: false, + }; + render(): React.ReactNode { + return + + 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( + + ); + } + + return
+ + + + { + const value = parseInt(e.currentTarget.value); + if (isNaN(value)) { + return; + } + song.setBpm(value); + }} + /> + + + { + const value = parseInt(e.currentTarget.value); + if (isNaN(value)) { + return; + } + song.setSwing(value); + }} + /> + + + + + +
; + } +} + +@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
+ {rows}
+
; + } +} + +@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') } };