mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-20 10:08:29 +00:00
Merge pull request #21 from tidalcycles/notes-and-numbers
added _asNumber + interprete numbers as midi
This commit is contained in:
commit
ad42e5eb61
441
package-lock.json
generated
441
package-lock.json
generated
@ -17,6 +17,7 @@
|
|||||||
"tone": "^14.7.77"
|
"tone": "^14.7.77"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tonaljs/tonal": "^4.6.5",
|
||||||
"mocha": "^9.1.4",
|
"mocha": "^9.1.4",
|
||||||
"ramda": "^0.28.0",
|
"ramda": "^0.28.0",
|
||||||
"snowpack": "^3.8.8"
|
"snowpack": "^3.8.8"
|
||||||
@ -450,6 +451,226 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tonaljs/abc-notation": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/abc-notation/-/abc-notation-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-1S0Jnx0NfDLgyhkQOMEHqOacELL6RUdPcWWUP+nAnsOsb9owvB9RKYLSzp5odd16FVUR7U8c/JLc2yxIRvSeJw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/array": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/array/-/array-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-7A3DbBQ+qIQ134FqE518b4tJ8V2a15Sn303JjHzgnqZqKrNh/s3wqwkL60F7LKcd09tcp+vIKQP/MYt4xMcRAA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/chord": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/chord/-/chord-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-Pjdel4aDVv4kcx9PW6Qozt5BB9nAt13AOExfzKztpgPmlBSy0SKHse7Jp1cA4MGAuLHU8dzVssTFYpCskEFw3w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/chord-detect": "^4.6.5",
|
||||||
|
"@tonaljs/chord-type": "^4.6.5",
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5",
|
||||||
|
"@tonaljs/scale-type": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/chord-detect": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/chord-detect/-/chord-detect-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-4xu53UP4kNTfdTNpAAVijhXcQ+ypJqmeMnsST08ZXSjoYfJUhmf5rWDWfz36KOTtNdCA6AbYgdtTYV/Xw0nd/w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/chord-type": "^4.6.5",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/chord-type": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/chord-type/-/chord-type-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-Ol4DDopqpZCF9odosO2i8I+plud3Ul7VWJGNvL+PPCf4Qnwuz87q3aJQDLNoRUz4VyW0u66mq3LyVh6A8kb6Ug==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/collection": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/collection/-/collection-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-bfPCotLJNB/tG1NrdbsQPLDKZB5jlMs7uPQ6RYKiNkaena3345ZKkbCGl5pj6YTXeDm/oblXiSbFAn7SlLRZdQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/core": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/core/-/core-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-t7Vx0+L3j/ubQj2AhI1H45D/K745np4DwJjJjXNi5FlGD+TL2wyw50dCwkHKGHsrLDqup1qqP6yN7LBpC6UwNg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/duration-value": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/duration-value/-/duration-value-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-zrXT0L/qsDQ6251Mlqz54vcUbYUB9xb6uJhlxUzc6VauXOt8UOfrdTULubRTXTaBwWt1h8J5n9pXTQmNGzNI9A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/interval": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/interval/-/interval-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-7EDWhqZ7Nnh9oD4ahRYJHLc799ACGxYL4hDHwMKD16B2MgXqPvDeDvwQ31qUuO0ruGz8tMb3FDlgg0Hplowcbw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/key": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/key/-/key-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-ZdZWb5IStx6CLRmdEjawR66CqNpoW3EVUua2nVZBMdgnNebWxt4nvgH/ZNvGlCQGFZkUZzRhCfTwqsS6e3OmSA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/note": "^4.6.5",
|
||||||
|
"@tonaljs/roman-numeral": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/midi": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/midi/-/midi-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-fJEZtNvV3M6yW1w+Tep60Rbv5PvuKszQcQzaJS1Loq5mHOKAzdmRfuJSpEpZBiaKEZ1WAMh1QKXYyOd+imyGQg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/mode": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/mode/-/mode-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-54iaON1rJ6q8fV5iuei8RGDxYhKBGGxZz3rjAxGSqdTUwBRVOdPqtzOkofThf9gRGYOMhzPp1BMbxbV+UCAPsA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/interval": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5",
|
||||||
|
"@tonaljs/scale-type": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/note": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/note/-/note-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-Y0/eTzcReXzfcSLLG4k/dLLayqbvh/XYIkybG/QMDyR0BREuJq0Sw+NavbzhTtO0dadIQb/qfe0GFq4k2xS+NQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/midi": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/pcset": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/pcset/-/pcset-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-oWAKflP3cREnUfScqsBzg2LLKNevxSnpDtrq8CPtwOAsrAa8PjQG07NQfhqIiFMjPUdgkDiER3qVA1n8dDwAJA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/progression": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/progression/-/progression-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-ijYEgMFQG4izHYUw5cRtBRNBuoYzmpGvb/tRiykhJNI6XIjekZEMiMsOMfb1u5q+EGvnVNXRmrluMRDIz2rmRw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/chord": "^4.6.5",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/roman-numeral": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/range": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/range/-/range-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-99cOvVJ3l4X0UJuTSa6qE87JriREnnWIsi3xo1/n7RoqFxnfi8YPh4SfJJyysvHcT18X4EfcTNde9ancMBVu6A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/midi": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/roman-numeral": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/roman-numeral/-/roman-numeral-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-bWYQNZWKmYDDcmbQQNwcWAHfTWanpzmvI0wplrMnGd4x0op5etwUEv+Yzjg0B1ef+E+zcU02Sl0WwRJhaDK3hg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/scale": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/scale/-/scale-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-isYDamelOBtcd5bEnJ8QV0Js7jKRwZ0FlFVE/+bUN3wsyo9u6KLL5gMyfH9RKdx74m8lE13JXYTXgKqe+AOa4A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/chord-type": "^4.6.5",
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/note": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5",
|
||||||
|
"@tonaljs/scale-type": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/scale-type": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/scale-type/-/scale-type-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-rwcDOYf2UifjLJhmuQ8f8bJSeOCMDQJ1lB7lzlqdFxes03OeQhdOEfrT0nPtW8BhBEvq4GMM2NA6CLxX8MTwOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/time-signature": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/time-signature/-/time-signature-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-OlZY4gdLd21WpMeAI1nS9E9zWcYU6oAzh6ptAUndqmVnFIrIWIWKCkWapdFx8dWdqrX8jqya3m4T33wmeo7w5Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@tonaljs/tonal": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/tonal/-/tonal-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-lmsWinI9dy7nQyzCEgDVeVAwJtsk4ey05cJZd6oa4QVuSFD+CR8ebaEiwT4/Na+W0kHrKicT3h0uYc2PJIvx5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@tonaljs/abc-notation": "^4.6.5",
|
||||||
|
"@tonaljs/array": "^4.6.5",
|
||||||
|
"@tonaljs/chord": "^4.6.5",
|
||||||
|
"@tonaljs/chord-type": "^4.6.5",
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/duration-value": "^4.6.2",
|
||||||
|
"@tonaljs/interval": "^4.6.5",
|
||||||
|
"@tonaljs/key": "^4.6.5",
|
||||||
|
"@tonaljs/midi": "^4.6.5",
|
||||||
|
"@tonaljs/mode": "^4.6.5",
|
||||||
|
"@tonaljs/note": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5",
|
||||||
|
"@tonaljs/progression": "^4.6.5",
|
||||||
|
"@tonaljs/range": "^4.6.5",
|
||||||
|
"@tonaljs/roman-numeral": "^4.6.5",
|
||||||
|
"@tonaljs/scale": "^4.6.5",
|
||||||
|
"@tonaljs/scale-type": "^4.6.5",
|
||||||
|
"@tonaljs/time-signature": "^4.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tootallnate/once": {
|
"node_modules/@tootallnate/once": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||||
@ -5353,6 +5574,226 @@
|
|||||||
"defer-to-connect": "^2.0.0"
|
"defer-to-connect": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@tonaljs/abc-notation": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/abc-notation/-/abc-notation-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-1S0Jnx0NfDLgyhkQOMEHqOacELL6RUdPcWWUP+nAnsOsb9owvB9RKYLSzp5odd16FVUR7U8c/JLc2yxIRvSeJw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/array": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/array/-/array-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-7A3DbBQ+qIQ134FqE518b4tJ8V2a15Sn303JjHzgnqZqKrNh/s3wqwkL60F7LKcd09tcp+vIKQP/MYt4xMcRAA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/chord": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/chord/-/chord-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-Pjdel4aDVv4kcx9PW6Qozt5BB9nAt13AOExfzKztpgPmlBSy0SKHse7Jp1cA4MGAuLHU8dzVssTFYpCskEFw3w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/chord-detect": "^4.6.5",
|
||||||
|
"@tonaljs/chord-type": "^4.6.5",
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5",
|
||||||
|
"@tonaljs/scale-type": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/chord-detect": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/chord-detect/-/chord-detect-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-4xu53UP4kNTfdTNpAAVijhXcQ+ypJqmeMnsST08ZXSjoYfJUhmf5rWDWfz36KOTtNdCA6AbYgdtTYV/Xw0nd/w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/chord-type": "^4.6.5",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/chord-type": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/chord-type/-/chord-type-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-Ol4DDopqpZCF9odosO2i8I+plud3Ul7VWJGNvL+PPCf4Qnwuz87q3aJQDLNoRUz4VyW0u66mq3LyVh6A8kb6Ug==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/collection": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/collection/-/collection-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-bfPCotLJNB/tG1NrdbsQPLDKZB5jlMs7uPQ6RYKiNkaena3345ZKkbCGl5pj6YTXeDm/oblXiSbFAn7SlLRZdQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@tonaljs/core": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/core/-/core-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-t7Vx0+L3j/ubQj2AhI1H45D/K745np4DwJjJjXNi5FlGD+TL2wyw50dCwkHKGHsrLDqup1qqP6yN7LBpC6UwNg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@tonaljs/duration-value": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/duration-value/-/duration-value-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-zrXT0L/qsDQ6251Mlqz54vcUbYUB9xb6uJhlxUzc6VauXOt8UOfrdTULubRTXTaBwWt1h8J5n9pXTQmNGzNI9A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@tonaljs/interval": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/interval/-/interval-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-7EDWhqZ7Nnh9oD4ahRYJHLc799ACGxYL4hDHwMKD16B2MgXqPvDeDvwQ31qUuO0ruGz8tMb3FDlgg0Hplowcbw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/key": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/key/-/key-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-ZdZWb5IStx6CLRmdEjawR66CqNpoW3EVUua2nVZBMdgnNebWxt4nvgH/ZNvGlCQGFZkUZzRhCfTwqsS6e3OmSA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/note": "^4.6.5",
|
||||||
|
"@tonaljs/roman-numeral": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/midi": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/midi/-/midi-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-fJEZtNvV3M6yW1w+Tep60Rbv5PvuKszQcQzaJS1Loq5mHOKAzdmRfuJSpEpZBiaKEZ1WAMh1QKXYyOd+imyGQg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/mode": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/mode/-/mode-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-54iaON1rJ6q8fV5iuei8RGDxYhKBGGxZz3rjAxGSqdTUwBRVOdPqtzOkofThf9gRGYOMhzPp1BMbxbV+UCAPsA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/interval": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5",
|
||||||
|
"@tonaljs/scale-type": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/note": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/note/-/note-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-Y0/eTzcReXzfcSLLG4k/dLLayqbvh/XYIkybG/QMDyR0BREuJq0Sw+NavbzhTtO0dadIQb/qfe0GFq4k2xS+NQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/midi": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/pcset": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/pcset/-/pcset-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-oWAKflP3cREnUfScqsBzg2LLKNevxSnpDtrq8CPtwOAsrAa8PjQG07NQfhqIiFMjPUdgkDiER3qVA1n8dDwAJA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/progression": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/progression/-/progression-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-ijYEgMFQG4izHYUw5cRtBRNBuoYzmpGvb/tRiykhJNI6XIjekZEMiMsOMfb1u5q+EGvnVNXRmrluMRDIz2rmRw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/chord": "^4.6.5",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/roman-numeral": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/range": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/range/-/range-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-99cOvVJ3l4X0UJuTSa6qE87JriREnnWIsi3xo1/n7RoqFxnfi8YPh4SfJJyysvHcT18X4EfcTNde9ancMBVu6A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/midi": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/roman-numeral": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/roman-numeral/-/roman-numeral-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-bWYQNZWKmYDDcmbQQNwcWAHfTWanpzmvI0wplrMnGd4x0op5etwUEv+Yzjg0B1ef+E+zcU02Sl0WwRJhaDK3hg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/core": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/scale": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/scale/-/scale-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-isYDamelOBtcd5bEnJ8QV0Js7jKRwZ0FlFVE/+bUN3wsyo9u6KLL5gMyfH9RKdx74m8lE13JXYTXgKqe+AOa4A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/chord-type": "^4.6.5",
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/note": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5",
|
||||||
|
"@tonaljs/scale-type": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/scale-type": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/scale-type/-/scale-type-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-rwcDOYf2UifjLJhmuQ8f8bJSeOCMDQJ1lB7lzlqdFxes03OeQhdOEfrT0nPtW8BhBEvq4GMM2NA6CLxX8MTwOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tonaljs/time-signature": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/time-signature/-/time-signature-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-OlZY4gdLd21WpMeAI1nS9E9zWcYU6oAzh6ptAUndqmVnFIrIWIWKCkWapdFx8dWdqrX8jqya3m4T33wmeo7w5Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@tonaljs/tonal": {
|
||||||
|
"version": "4.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tonaljs/tonal/-/tonal-4.6.5.tgz",
|
||||||
|
"integrity": "sha512-lmsWinI9dy7nQyzCEgDVeVAwJtsk4ey05cJZd6oa4QVuSFD+CR8ebaEiwT4/Na+W0kHrKicT3h0uYc2PJIvx5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tonaljs/abc-notation": "^4.6.5",
|
||||||
|
"@tonaljs/array": "^4.6.5",
|
||||||
|
"@tonaljs/chord": "^4.6.5",
|
||||||
|
"@tonaljs/chord-type": "^4.6.5",
|
||||||
|
"@tonaljs/collection": "^4.6.2",
|
||||||
|
"@tonaljs/core": "^4.6.5",
|
||||||
|
"@tonaljs/duration-value": "^4.6.2",
|
||||||
|
"@tonaljs/interval": "^4.6.5",
|
||||||
|
"@tonaljs/key": "^4.6.5",
|
||||||
|
"@tonaljs/midi": "^4.6.5",
|
||||||
|
"@tonaljs/mode": "^4.6.5",
|
||||||
|
"@tonaljs/note": "^4.6.5",
|
||||||
|
"@tonaljs/pcset": "^4.6.5",
|
||||||
|
"@tonaljs/progression": "^4.6.5",
|
||||||
|
"@tonaljs/range": "^4.6.5",
|
||||||
|
"@tonaljs/roman-numeral": "^4.6.5",
|
||||||
|
"@tonaljs/scale": "^4.6.5",
|
||||||
|
"@tonaljs/scale-type": "^4.6.5",
|
||||||
|
"@tonaljs/time-signature": "^4.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@tootallnate/once": {
|
"@tootallnate/once": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/yaxu/strudel#readme",
|
"homepage": "https://github.com/yaxu/strudel#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tonaljs/tonal": "^4.6.5",
|
||||||
"mocha": "^9.1.4",
|
"mocha": "^9.1.4",
|
||||||
"ramda": "^0.28.0",
|
"ramda": "^0.28.0",
|
||||||
"snowpack": "^3.8.8"
|
"snowpack": "^3.8.8"
|
||||||
|
|||||||
11
repl/package-lock.json
generated
11
repl/package-lock.json
generated
@ -21,6 +21,7 @@
|
|||||||
"shift-regexp-acceptor": "^2.0.3",
|
"shift-regexp-acceptor": "^2.0.3",
|
||||||
"shift-spec": "^2018.0.2",
|
"shift-spec": "^2018.0.2",
|
||||||
"tone": "^14.7.77",
|
"tone": "^14.7.77",
|
||||||
|
"tune.js": "^1.0.1",
|
||||||
"webmidi": "^2.5.2"
|
"webmidi": "^2.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -11586,6 +11587,11 @@
|
|||||||
"node": ">=0.6.x"
|
"node": ">=0.6.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tune.js": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tune.js/-/tune.js-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-w0gHN/2FgY2qQJPSsz7m8td8gGB22WbhWeicPxStnlg8Mp75gHBCJkBX2/KtXcY5hJa9XPGc1DU84ZkopTpjEQ=="
|
||||||
|
},
|
||||||
"node_modules/tunnel-agent": {
|
"node_modules/tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
@ -21042,6 +21048,11 @@
|
|||||||
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
|
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"tune.js": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tune.js/-/tune.js-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-w0gHN/2FgY2qQJPSsz7m8td8gGB22WbhWeicPxStnlg8Mp75gHBCJkBX2/KtXcY5hJa9XPGc1DU84ZkopTpjEQ=="
|
||||||
|
},
|
||||||
"tunnel-agent": {
|
"tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
"shift-regexp-acceptor": "^2.0.3",
|
"shift-regexp-acceptor": "^2.0.3",
|
||||||
"shift-spec": "^2018.0.2",
|
"shift-spec": "^2018.0.2",
|
||||||
"tone": "^14.7.77",
|
"tone": "^14.7.77",
|
||||||
|
"tune.js": "^1.0.1",
|
||||||
"webmidi": "^2.5.2"
|
"webmidi": "^2.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export default {
|
|||||||
},
|
},
|
||||||
packageOptions: {
|
packageOptions: {
|
||||||
/* ... */
|
/* ... */
|
||||||
knownEntrypoints: ['fraction.js', 'codemirror'],
|
knownEntrypoints: ['fraction.js', 'codemirror', 'shift-ast', 'ramda'],
|
||||||
},
|
},
|
||||||
devOptions: {
|
devOptions: {
|
||||||
tailwindConfig: './tailwind.config.js',
|
tailwindConfig: './tailwind.config.js',
|
||||||
|
|||||||
@ -38,3 +38,9 @@ export const markEvent = (editor) => (time, event) => {
|
|||||||
// }, '+' + event.duration * 0.5);
|
// }, '+' + event.duration * 0.5);
|
||||||
}, event.duration * 0.9 * 1000);
|
}, event.duration * 0.9 * 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// idea: to improve highlighting, all patterns that appear anywhere in the code could be queried seperately
|
||||||
|
// the created events could then be used to highlight primitives as long as they are active
|
||||||
|
// this would create a less flickery output, with no duplications
|
||||||
|
// it would be seperated completely from the querying that happens to get the sound output
|
||||||
|
// it would also allow highlighting primitives that don't even end up in the sounding events (just for visual purposes)
|
||||||
|
|||||||
@ -2,6 +2,9 @@ import * as strudel from '../../strudel.mjs';
|
|||||||
import './tone';
|
import './tone';
|
||||||
import './midi';
|
import './midi';
|
||||||
import './voicings';
|
import './voicings';
|
||||||
|
import './tonal.mjs';
|
||||||
|
import './xen.mjs';
|
||||||
|
import './tune.mjs';
|
||||||
import './tonal';
|
import './tonal';
|
||||||
import gist from './gist.js';
|
import gist from './gist.js';
|
||||||
import shapeshifter from './shapeshifter';
|
import shapeshifter from './shapeshifter';
|
||||||
|
|||||||
@ -1,19 +1,12 @@
|
|||||||
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
||||||
import { Pattern as _Pattern } from '../../strudel.mjs';
|
import { Pattern as _Pattern } from '../../strudel.mjs';
|
||||||
|
import { mod, tokenizeNote } from '../../util.mjs';
|
||||||
|
|
||||||
const Pattern = _Pattern as any;
|
const Pattern = _Pattern; // as any;
|
||||||
|
|
||||||
// modulo that works with negative numbers e.g. mod(-1, 3) = 2
|
|
||||||
const mod = (n: number, m: number): number => (n < 0 ? mod(n + m, m) : n % m);
|
|
||||||
|
|
||||||
export function intervalDirection(from, to, direction = 1) {
|
|
||||||
const sign = Math.sign(direction);
|
|
||||||
const interval = sign < 0 ? Interval.distance(to, from) : Interval.distance(from, to);
|
|
||||||
return (sign < 0 ? '-' : '') + interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
// transpose note inside scale by offset steps
|
// transpose note inside scale by offset steps
|
||||||
function scaleTranspose(scale: string, offset: number, note: string) {
|
// function scaleTranspose(scale: string, offset: number, note: string) {
|
||||||
|
export function scaleTranspose(scale, offset, note) {
|
||||||
let [tonic, scaleName] = Scale.tokenize(scale);
|
let [tonic, scaleName] = Scale.tokenize(scale);
|
||||||
let { notes } = Scale.get(`${tonic} ${scaleName}`);
|
let { notes } = Scale.get(`${tonic} ${scaleName}`);
|
||||||
notes = notes.map((note) => Note.get(note).pc); // use only pc!
|
notes = notes.map((note) => Note.get(note).pc); // use only pc!
|
||||||
@ -45,10 +38,11 @@ function scaleTranspose(scale: string, offset: number, note: string) {
|
|||||||
return n + o;
|
return n + o;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
|
// Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
|
||||||
|
Pattern.prototype._transpose = function (intervalOrSemitones) {
|
||||||
return this._withEvent((event) => {
|
return this._withEvent((event) => {
|
||||||
const interval = !isNaN(Number(intervalOrSemitones))
|
const interval = !isNaN(Number(intervalOrSemitones))
|
||||||
? Interval.fromSemitones(intervalOrSemitones as number)
|
? Interval.fromSemitones(intervalOrSemitones /* as number */)
|
||||||
: String(intervalOrSemitones);
|
: String(intervalOrSemitones);
|
||||||
if (typeof event.value === 'number') {
|
if (typeof event.value === 'number') {
|
||||||
const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval;
|
const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval;
|
||||||
@ -66,7 +60,7 @@ Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
|
|||||||
// e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or
|
// e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or
|
||||||
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
|
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
|
||||||
|
|
||||||
Pattern.prototype._scaleTranspose = function (offset: number | string) {
|
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
|
||||||
return this._withEvent((event) => {
|
return this._withEvent((event) => {
|
||||||
if (!event.context.scale) {
|
if (!event.context.scale) {
|
||||||
throw new Error('can only use scaleTranspose after .scale');
|
throw new Error('can only use scaleTranspose after .scale');
|
||||||
@ -77,7 +71,7 @@ Pattern.prototype._scaleTranspose = function (offset: number | string) {
|
|||||||
return event.withValue(() => scaleTranspose(event.context.scale, Number(offset), event.value));
|
return event.withValue(() => scaleTranspose(event.context.scale, Number(offset), event.value));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
Pattern.prototype._scale = function (scale: string) {
|
Pattern.prototype._scale = function (scale /* : string */) {
|
||||||
return this._withEvent((event) => {
|
return this._withEvent((event) => {
|
||||||
let note = event.value;
|
let note = event.value;
|
||||||
const asNumber = Number(note);
|
const asNumber = Number(note);
|
||||||
@ -20,6 +20,7 @@ import {
|
|||||||
Players,
|
Players,
|
||||||
} from 'tone';
|
} from 'tone';
|
||||||
import { Piano } from '@tonejs/piano';
|
import { Piano } from '@tonejs/piano';
|
||||||
|
import { getPlayableNoteValue } from '../../util.mjs';
|
||||||
|
|
||||||
// what about
|
// what about
|
||||||
// https://www.charlie-roberts.com/gibberish/playground/
|
// https://www.charlie-roberts.com/gibberish/playground/
|
||||||
@ -31,24 +32,23 @@ Pattern.prototype.tone = function (instrument) {
|
|||||||
// instrument.toDestination();
|
// instrument.toDestination();
|
||||||
return this._withEvent((event) => {
|
return this._withEvent((event) => {
|
||||||
const onTrigger = (time, event) => {
|
const onTrigger = (time, event) => {
|
||||||
let note = event.value;
|
let note;
|
||||||
let velocity = event.context?.velocity ?? 0.75;
|
let velocity = event.context?.velocity ?? 0.75;
|
||||||
switch (instrument.constructor.name) {
|
switch (instrument.constructor.name) {
|
||||||
case 'PluckSynth':
|
case 'PluckSynth':
|
||||||
// note = getPlayableNoteValue(event);
|
note = getPlayableNoteValue(event);
|
||||||
// velocity?
|
|
||||||
instrument.triggerAttack(note, time);
|
instrument.triggerAttack(note, time);
|
||||||
break;
|
break;
|
||||||
case 'NoiseSynth':
|
case 'NoiseSynth':
|
||||||
instrument.triggerAttackRelease(event.duration, time); // noise has no value
|
instrument.triggerAttackRelease(event.duration, time); // noise has no value
|
||||||
break;
|
break;
|
||||||
case 'Piano':
|
case 'Piano':
|
||||||
// note = getPlayableNoteValue(event);
|
note = getPlayableNoteValue(event);
|
||||||
instrument.keyDown({ note, time, velocity: 0.5 });
|
instrument.keyDown({ note, time, velocity: 0.5 });
|
||||||
instrument.keyUp({ note, time: time + event.duration, velocity });
|
instrument.keyUp({ note, time: time + event.duration, velocity });
|
||||||
break;
|
break;
|
||||||
case 'Sampler':
|
case 'Sampler':
|
||||||
// note = getPlayableNoteValue(event);
|
note = getPlayableNoteValue(event);
|
||||||
instrument.triggerAttackRelease(note, event.duration, time, velocity);
|
instrument.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
break;
|
break;
|
||||||
case 'Players':
|
case 'Players':
|
||||||
@ -61,7 +61,7 @@ Pattern.prototype.tone = function (instrument) {
|
|||||||
player.stop(time + event.duration);
|
player.stop(time + event.duration);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// note = getPlayableNoteValue(event);
|
note = getPlayableNoteValue(event);
|
||||||
instrument.triggerAttackRelease(note, event.duration, time, velocity);
|
instrument.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
16
repl/src/tune.mjs
Normal file
16
repl/src/tune.mjs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Tune from './tunejs.js';
|
||||||
|
import { Pattern } from '../../strudel.mjs';
|
||||||
|
|
||||||
|
Pattern.prototype._tune = function (scale, tonic = 220) {
|
||||||
|
const tune = new Tune();
|
||||||
|
if (!tune.isValidScale(scale)) {
|
||||||
|
throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html');
|
||||||
|
}
|
||||||
|
tune.loadScale(scale);
|
||||||
|
tune.tonicize(tonic);
|
||||||
|
return this._asNumber()._withEvent((event) => {
|
||||||
|
return event.withValue(() => tune.note(event.value)).setContext({ ...event.context, type: 'frequency' });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Pattern.prototype.define('tune', (scale, pat) => pat.tune(scale), { composable: true, patternified: true });
|
||||||
233
repl/src/tunejs.js
Normal file
233
repl/src/tunejs.js
Normal file
File diff suppressed because one or more lines are too long
@ -516,3 +516,31 @@ export const wavyKalimba = `sampler({
|
|||||||
.fast(1)
|
.fast(1)
|
||||||
})`;
|
})`;
|
||||||
|
|
||||||
|
export const jemblung = `() => {
|
||||||
|
const delay = new FeedbackDelay(1/8, .6).chain(vol(0.15), out());
|
||||||
|
const snare = noise({type:'white',...adsr(0,0.2,0)}).chain(lowpass(5000),vol(1.8),out());
|
||||||
|
const s = polysynth().set({...osc('sawtooth4'),...adsr(0.01,.2,.6,0.2)}).chain(vol(.23).connect(delay),out());
|
||||||
|
return stack(
|
||||||
|
stack(
|
||||||
|
"0 1 4 [3!2 5]".edit(
|
||||||
|
// chords
|
||||||
|
x=>x.add("0,3").duration("0.05!3 0.02"),
|
||||||
|
// bass
|
||||||
|
x=>x.add("-8").struct("x*8").duration(0.1)
|
||||||
|
),
|
||||||
|
// melody
|
||||||
|
"12 11*3 12 ~".duration(0.005)
|
||||||
|
)
|
||||||
|
.add("<0 1>")
|
||||||
|
.tune("jemblung2")
|
||||||
|
//.mul(22/5).round().xen("22edo")
|
||||||
|
//.mul(12/5).round().xen("12edo")
|
||||||
|
.tone(s),
|
||||||
|
// kick
|
||||||
|
"[c2 ~]*2".duration(0.05).tone(membrane().chain(out())),
|
||||||
|
// snare
|
||||||
|
"[~ c1]*2".early(0.001).tone(snare),
|
||||||
|
// hihat
|
||||||
|
"c2*8".tone(noise().chain(highpass(6000),vol(0.5).connect(delay),out())),
|
||||||
|
).slow(3)
|
||||||
|
}`;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useState, useMemo } from 'react';
|
import { useCallback, useState, useMemo } from 'react';
|
||||||
import { isNote } from 'tone';
|
import { getPlayableNoteValue } from '../../util.mjs';
|
||||||
import { evaluate } from './evaluate';
|
import { evaluate } from './evaluate';
|
||||||
import type { Pattern } from './types';
|
import type { Pattern } from './types';
|
||||||
import useCycle from './useCycle';
|
import useCycle from './useCycle';
|
||||||
@ -63,11 +63,8 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }: any)
|
|||||||
onEvent?.(event);
|
onEvent?.(event);
|
||||||
const { onTrigger, velocity } = event.context;
|
const { onTrigger, velocity } = event.context;
|
||||||
if (!onTrigger) {
|
if (!onTrigger) {
|
||||||
const note = event.value;
|
|
||||||
if (!isNote(note)) {
|
|
||||||
throw new Error('not a note: ' + note);
|
|
||||||
}
|
|
||||||
if (defaultSynth) {
|
if (defaultSynth) {
|
||||||
|
const note = getPlayableNoteValue(event);
|
||||||
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity);
|
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('no defaultSynth passed to useRepl.');
|
throw new Error('no defaultSynth passed to useRepl.');
|
||||||
|
|||||||
62
repl/src/xen.mjs
Normal file
62
repl/src/xen.mjs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { Pattern } from '../../strudel.mjs';
|
||||||
|
import { mod } from '../../util.mjs';
|
||||||
|
|
||||||
|
function edo(name) {
|
||||||
|
if (!/^[1-9]+[0-9]*edo$/.test(name)) {
|
||||||
|
throw new Error('not an edo scale: "' + name + '"');
|
||||||
|
}
|
||||||
|
const [_, divisions] = name.match(/^([1-9]+[0-9]*)edo$/);
|
||||||
|
return Array.from({ length: divisions }, (_, i) => Math.pow(2, i / divisions));
|
||||||
|
}
|
||||||
|
|
||||||
|
const presets = {
|
||||||
|
'12ji': [1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4, 4 / 3, 45 / 32, 3 / 2, 8 / 5, 5 / 3, 16 / 9, 15 / 8],
|
||||||
|
};
|
||||||
|
|
||||||
|
function withBase(freq, scale) {
|
||||||
|
return scale.map((r) => r * freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultBase = 220;
|
||||||
|
|
||||||
|
function getXenScale(scale, indices) {
|
||||||
|
if (typeof scale === 'string') {
|
||||||
|
if (/^[1-9]+[0-9]*edo$/.test(scale)) {
|
||||||
|
scale = edo(scale);
|
||||||
|
} else if (presets[scale]) {
|
||||||
|
scale = presets[scale];
|
||||||
|
} else {
|
||||||
|
throw new Error('unknown scale name: "' + scale + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scale = withBase(defaultBase, scale);
|
||||||
|
if (!indices) {
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
return scale.filter((_, i) => indices.includes(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
function xenOffset(xenScale, offset, index = 0) {
|
||||||
|
const i = mod(index + offset, xenScale.length);
|
||||||
|
const oct = Math.floor(offset / xenScale.length);
|
||||||
|
return xenScale[i] * Math.pow(2, oct);
|
||||||
|
}
|
||||||
|
|
||||||
|
// scaleNameOrRatios: string || number[], steps?: number
|
||||||
|
Pattern.prototype._xen = function (scaleNameOrRatios, steps) {
|
||||||
|
return this._asNumber()._withEvent((event) => {
|
||||||
|
const scale = getXenScale(scaleNameOrRatios);
|
||||||
|
steps = steps || scale.length;
|
||||||
|
const frequency = xenOffset(scale, event.value);
|
||||||
|
return event.withValue(() => frequency).setContext({ ...event.context, type: 'frequency' });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Pattern.prototype.tuning = function (steps) {
|
||||||
|
return this._asNumber()._withEvent((event) => {
|
||||||
|
const frequency = xenOffset(steps, event.value);
|
||||||
|
return event.withValue(() => frequency).setContext({ ...event.context, type: 'frequency' });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Pattern.prototype.define('xen', (scale, pat) => pat.xen(scale), { composable: true, patternified: true });
|
||||||
|
// Pattern.prototype.define('tuning', (scale, pat) => pat.xen(scale), { composable: true, patternified: false });
|
||||||
34
strudel.mjs
34
strudel.mjs
@ -1,5 +1,6 @@
|
|||||||
import Fraction from 'fraction.js'
|
import Fraction from 'fraction.js'
|
||||||
import { compose } from 'ramda'; // will remove this as soon as compose is implemented here
|
import { compose } from 'ramda'; // will remove this as soon as compose is implemented here
|
||||||
|
import { isNote, toMidi } from './util.mjs';
|
||||||
|
|
||||||
// Removes 'None' values from given list
|
// Removes 'None' values from given list
|
||||||
const removeUndefineds = xs => xs.filter(x => x != undefined)
|
const removeUndefineds = xs => xs.filter(x => x != undefined)
|
||||||
@ -467,20 +468,45 @@ class Pattern {
|
|||||||
return this.fmap(func).appLeft(reify(other))
|
return this.fmap(func).appLeft(reify(other))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_asNumber() {
|
||||||
|
return this._withEvent(event => {
|
||||||
|
const asNumber = Number(event.value);
|
||||||
|
if (!isNaN(asNumber)) {
|
||||||
|
return event.withValue(() => asNumber);
|
||||||
|
}
|
||||||
|
const specialValue = {
|
||||||
|
e: Math.E,
|
||||||
|
pi: Math.PI,
|
||||||
|
}[event.value];
|
||||||
|
if (typeof specialValue !== 'undefined') {
|
||||||
|
return event.withValue(() => specialValue);
|
||||||
|
}
|
||||||
|
if (isNote(event.value)) {
|
||||||
|
// set context type to midi to let the player know its meant as midi number and not as frequency
|
||||||
|
return new Hap(event.whole, event.part, toMidi(event.value), { ...event.context, type: 'midi' });
|
||||||
|
}
|
||||||
|
throw new Error('cannot parse as number: "' + event.value + '"');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
add(other) {
|
add(other) {
|
||||||
return this._opleft(other, a => b => a + b)
|
return this._asNumber()._opleft(other, a => b => a + b)
|
||||||
}
|
}
|
||||||
|
|
||||||
sub(other) {
|
sub(other) {
|
||||||
return this._opleft(other, a => b => a - b)
|
return this._asNumber()._opleft(other, a => b => a - b)
|
||||||
}
|
}
|
||||||
|
|
||||||
mul(other) {
|
mul(other) {
|
||||||
return this._opleft(other, a => b => a * b)
|
return this._asNumber()._opleft(other, a => b => a * b)
|
||||||
}
|
}
|
||||||
|
|
||||||
div(other) {
|
div(other) {
|
||||||
return this._opleft(other, a => b => a / b)
|
return this._asNumber()._opleft(other, a => b => a / b)
|
||||||
|
}
|
||||||
|
|
||||||
|
round() {
|
||||||
|
return this._asNumber().fmap((v) => Math.round(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
union(other) {
|
union(other) {
|
||||||
|
|||||||
12
test/tonal.test.mjs
Normal file
12
test/tonal.test.mjs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { scaleTranspose } from '../repl/src/tonal.mjs';
|
||||||
|
|
||||||
|
describe('scaleTranspose', () => {
|
||||||
|
it('should transpose inside scale', () => {
|
||||||
|
assert.equal(scaleTranspose('C major', 1, 'C3'), 'D3');
|
||||||
|
assert.equal(scaleTranspose('C major', 2, 'E3'), 'G3');
|
||||||
|
assert.equal(scaleTranspose('C major', 1, 'E3'), 'F3');
|
||||||
|
assert.equal(scaleTranspose('C major', 1, 'G3'), 'A3');
|
||||||
|
assert.equal(scaleTranspose('C major', 1, 'C4'), 'D4');
|
||||||
|
});
|
||||||
|
});
|
||||||
85
test/util.test.mjs
Normal file
85
test/util.test.mjs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { isNote, tokenizeNote, toMidi, mod } from '../util.mjs';
|
||||||
|
|
||||||
|
describe('isNote', () => {
|
||||||
|
it('should recognize notes without accidentals', () => {
|
||||||
|
'C3 D3 E3 F3 G3 A3 B3 C4 D5 c5 d5 e5'.split(' ').forEach((note) => {
|
||||||
|
assert.equal(isNote(note), true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should recognize notes with accidentals', () => {
|
||||||
|
'C#3 D##3 Eb3 Fbb3 Bb5'.split(' ').forEach((note) => {
|
||||||
|
assert.equal(isNote(note), true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should not recognize invalid notes', () => {
|
||||||
|
assert.equal(isNote('H5'), false);
|
||||||
|
assert.equal(isNote('C'), false);
|
||||||
|
assert.equal(isNote('X'), false);
|
||||||
|
assert.equal(isNote(1), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isNote', () => {
|
||||||
|
it('should tokenize notes without accidentals', () => {
|
||||||
|
assert.deepStrictEqual(tokenizeNote('C3'), ['C', '', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('D3'), ['D', '', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('E3'), ['E', '', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('F3'), ['F', '', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('G3'), ['G', '', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('A3'), ['A', '', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('B3'), ['B', '', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('C4'), ['C', '', 4]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('D5'), ['D', '', 5]);
|
||||||
|
});
|
||||||
|
it('should tokenize notes with accidentals', () => {
|
||||||
|
assert.deepStrictEqual(tokenizeNote('C#3'), ['C', '#', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('D##3'), ['D', '##', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('Eb3'), ['E', 'b', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('Fbb3'), ['F', 'bb', 3]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('Bb5'), ['B', 'b', 5]);
|
||||||
|
});
|
||||||
|
it('should tokenize notes without octave', () => {
|
||||||
|
assert.deepStrictEqual(tokenizeNote('C'), ['C', '', undefined]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('C#'), ['C', '#', undefined]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('Bb'), ['B', 'b', undefined]);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('Bbb'), ['B', 'bb', undefined]);
|
||||||
|
});
|
||||||
|
it('should not tokenize invalid notes', () => {
|
||||||
|
assert.deepStrictEqual(tokenizeNote('X'), []);
|
||||||
|
assert.deepStrictEqual(tokenizeNote('asfasf'), []);
|
||||||
|
assert.deepStrictEqual(tokenizeNote(123), []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('toMidi', () => {
|
||||||
|
it('should turn notes into midi', () => {
|
||||||
|
assert.equal(toMidi('A4'), 69);
|
||||||
|
assert.equal(toMidi('C4'), 60);
|
||||||
|
assert.equal(toMidi('Db4'), 61);
|
||||||
|
assert.equal(toMidi('C3'), 48);
|
||||||
|
assert.equal(toMidi('Cb3'), 47);
|
||||||
|
assert.equal(toMidi('Cbb3'), 46);
|
||||||
|
assert.equal(toMidi('C#3'), 49);
|
||||||
|
assert.equal(toMidi('C#3'), 49);
|
||||||
|
assert.equal(toMidi('C##3'), 50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('mod', () => {
|
||||||
|
it('should work like regular modulo with positive numbers', () => {
|
||||||
|
assert.equal(mod(0, 3), 0);
|
||||||
|
assert.equal(mod(1, 3), 1);
|
||||||
|
assert.equal(mod(2, 3), 2);
|
||||||
|
assert.equal(mod(3, 3), 0);
|
||||||
|
assert.equal(mod(4, 3), 1);
|
||||||
|
assert.equal(mod(4, 2), 0);
|
||||||
|
});
|
||||||
|
it('should work with negative numbers', () => {
|
||||||
|
assert.equal(mod(-1, 3), 2);
|
||||||
|
assert.equal(mod(-2, 3), 1);
|
||||||
|
assert.equal(mod(-3, 3), 0);
|
||||||
|
assert.equal(mod(-4, 3), 2);
|
||||||
|
assert.equal(mod(-5, 3), 1);
|
||||||
|
assert.equal(mod(-3, 2), 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
41
util.mjs
Normal file
41
util.mjs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// returns true if the given string is a note
|
||||||
|
export const isNote = (name) => /^[a-gA-G][#b]*[0-9]$/.test(name);
|
||||||
|
export const tokenizeNote = (note) => {
|
||||||
|
if (typeof note !== 'string') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#b]*)([0-9])?$/)?.slice(1) || [];
|
||||||
|
if (!pc) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [pc, acc, oct ? Number(oct) : undefined];
|
||||||
|
};
|
||||||
|
|
||||||
|
// turns the given note into its midi number representation
|
||||||
|
export const toMidi = (note) => {
|
||||||
|
const [pc, acc, oct] = tokenizeNote(note);
|
||||||
|
if (!pc) {
|
||||||
|
throw new Error('not a note: "' + note + '"');
|
||||||
|
}
|
||||||
|
const chroma = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[pc.toLowerCase()];
|
||||||
|
const offset = acc?.split('').reduce((o, char) => o + { '#': 1, b: -1 }[char], 0) || 0;
|
||||||
|
return (Number(oct) + 1) * 12 + chroma + offset;
|
||||||
|
};
|
||||||
|
export const fromMidi = (n) => {
|
||||||
|
return Math.pow(2, (n - 69) / 12) * 440;
|
||||||
|
};
|
||||||
|
|
||||||
|
// modulo that works with negative numbers e.g. mod(-1, 3) = 2
|
||||||
|
// const mod = (n: number, m: number): number => (n < 0 ? mod(n + m, m) : n % m);
|
||||||
|
export const mod = (n, m) => (n < 0 ? mod(n + m, m) : n % m);
|
||||||
|
|
||||||
|
export const getPlayableNoteValue = (event) => {
|
||||||
|
let { value: note, context } = event;
|
||||||
|
// if value is number => interpret as midi number as long as its not marked as frequency
|
||||||
|
if (typeof note === 'number' && context.type !== 'frequency') {
|
||||||
|
note = fromMidi(event.value);
|
||||||
|
} else if (typeof note === 'string' && !isNote(note)) {
|
||||||
|
throw new Error('not a note: ' + note);
|
||||||
|
}
|
||||||
|
return note;
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user