mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-12 06:08:34 +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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tonaljs/tonal": "^4.6.5",
|
||||
"mocha": "^9.1.4",
|
||||
"ramda": "^0.28.0",
|
||||
"snowpack": "^3.8.8"
|
||||
@ -450,6 +451,226 @@
|
||||
"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": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||
@ -5353,6 +5574,226 @@
|
||||
"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": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/yaxu/strudel#readme",
|
||||
"devDependencies": {
|
||||
"@tonaljs/tonal": "^4.6.5",
|
||||
"mocha": "^9.1.4",
|
||||
"ramda": "^0.28.0",
|
||||
"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-spec": "^2018.0.2",
|
||||
"tone": "^14.7.77",
|
||||
"tune.js": "^1.0.1",
|
||||
"webmidi": "^2.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -11586,6 +11587,11 @@
|
||||
"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": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
@ -21042,6 +21048,11 @@
|
||||
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
|
||||
"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": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
"shift-regexp-acceptor": "^2.0.3",
|
||||
"shift-spec": "^2018.0.2",
|
||||
"tone": "^14.7.77",
|
||||
"tune.js": "^1.0.1",
|
||||
"webmidi": "^2.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -28,7 +28,7 @@ export default {
|
||||
},
|
||||
packageOptions: {
|
||||
/* ... */
|
||||
knownEntrypoints: ['fraction.js', 'codemirror'],
|
||||
knownEntrypoints: ['fraction.js', 'codemirror', 'shift-ast', 'ramda'],
|
||||
},
|
||||
devOptions: {
|
||||
tailwindConfig: './tailwind.config.js',
|
||||
|
||||
@ -38,3 +38,9 @@ export const markEvent = (editor) => (time, event) => {
|
||||
// }, '+' + event.duration * 0.5);
|
||||
}, 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 './midi';
|
||||
import './voicings';
|
||||
import './tonal.mjs';
|
||||
import './xen.mjs';
|
||||
import './tune.mjs';
|
||||
import './tonal';
|
||||
import gist from './gist.js';
|
||||
import shapeshifter from './shapeshifter';
|
||||
|
||||
@ -1,19 +1,12 @@
|
||||
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
||||
import { Pattern as _Pattern } from '../../strudel.mjs';
|
||||
import { mod, tokenizeNote } from '../../util.mjs';
|
||||
|
||||
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;
|
||||
}
|
||||
const Pattern = _Pattern; // as any;
|
||||
|
||||
// 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 { notes } = Scale.get(`${tonic} ${scaleName}`);
|
||||
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;
|
||||
}
|
||||
|
||||
Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
|
||||
// Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
|
||||
Pattern.prototype._transpose = function (intervalOrSemitones) {
|
||||
return this._withEvent((event) => {
|
||||
const interval = !isNaN(Number(intervalOrSemitones))
|
||||
? Interval.fromSemitones(intervalOrSemitones as number)
|
||||
? Interval.fromSemitones(intervalOrSemitones /* as number */)
|
||||
: String(intervalOrSemitones);
|
||||
if (typeof event.value === 'number') {
|
||||
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
|
||||
// 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) => {
|
||||
if (!event.context.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));
|
||||
});
|
||||
};
|
||||
Pattern.prototype._scale = function (scale: string) {
|
||||
Pattern.prototype._scale = function (scale /* : string */) {
|
||||
return this._withEvent((event) => {
|
||||
let note = event.value;
|
||||
const asNumber = Number(note);
|
||||
@ -20,6 +20,7 @@ import {
|
||||
Players,
|
||||
} from 'tone';
|
||||
import { Piano } from '@tonejs/piano';
|
||||
import { getPlayableNoteValue } from '../../util.mjs';
|
||||
|
||||
// what about
|
||||
// https://www.charlie-roberts.com/gibberish/playground/
|
||||
@ -31,24 +32,23 @@ Pattern.prototype.tone = function (instrument) {
|
||||
// instrument.toDestination();
|
||||
return this._withEvent((event) => {
|
||||
const onTrigger = (time, event) => {
|
||||
let note = event.value;
|
||||
let note;
|
||||
let velocity = event.context?.velocity ?? 0.75;
|
||||
switch (instrument.constructor.name) {
|
||||
case 'PluckSynth':
|
||||
// note = getPlayableNoteValue(event);
|
||||
// velocity?
|
||||
note = getPlayableNoteValue(event);
|
||||
instrument.triggerAttack(note, time);
|
||||
break;
|
||||
case 'NoiseSynth':
|
||||
instrument.triggerAttackRelease(event.duration, time); // noise has no value
|
||||
break;
|
||||
case 'Piano':
|
||||
// note = getPlayableNoteValue(event);
|
||||
note = getPlayableNoteValue(event);
|
||||
instrument.keyDown({ note, time, velocity: 0.5 });
|
||||
instrument.keyUp({ note, time: time + event.duration, velocity });
|
||||
break;
|
||||
case 'Sampler':
|
||||
// note = getPlayableNoteValue(event);
|
||||
note = getPlayableNoteValue(event);
|
||||
instrument.triggerAttackRelease(note, event.duration, time, velocity);
|
||||
break;
|
||||
case 'Players':
|
||||
@ -61,7 +61,7 @@ Pattern.prototype.tone = function (instrument) {
|
||||
player.stop(time + event.duration);
|
||||
break;
|
||||
default:
|
||||
// note = getPlayableNoteValue(event);
|
||||
note = getPlayableNoteValue(event);
|
||||
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)
|
||||
})`;
|
||||
|
||||
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 { isNote } from 'tone';
|
||||
import { getPlayableNoteValue } from '../../util.mjs';
|
||||
import { evaluate } from './evaluate';
|
||||
import type { Pattern } from './types';
|
||||
import useCycle from './useCycle';
|
||||
@ -63,11 +63,8 @@ function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw }: any)
|
||||
onEvent?.(event);
|
||||
const { onTrigger, velocity } = event.context;
|
||||
if (!onTrigger) {
|
||||
const note = event.value;
|
||||
if (!isNote(note)) {
|
||||
throw new Error('not a note: ' + note);
|
||||
}
|
||||
if (defaultSynth) {
|
||||
const note = getPlayableNoteValue(event);
|
||||
defaultSynth.triggerAttackRelease(note, event.duration, time, velocity);
|
||||
} else {
|
||||
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 { 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
|
||||
const removeUndefineds = xs => xs.filter(x => x != undefined)
|
||||
@ -467,20 +468,45 @@ class Pattern {
|
||||
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) {
|
||||
return this._opleft(other, a => b => a + b)
|
||||
return this._asNumber()._opleft(other, a => b => a + b)
|
||||
}
|
||||
|
||||
sub(other) {
|
||||
return this._opleft(other, a => b => a - b)
|
||||
return this._asNumber()._opleft(other, a => b => a - b)
|
||||
}
|
||||
|
||||
mul(other) {
|
||||
return this._opleft(other, a => b => a * b)
|
||||
return this._asNumber()._opleft(other, a => b => a * b)
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
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