├── .gitignore ├── src ├── Sounds │ ├── HiHat_G.wav │ ├── Kick_C.wav │ ├── Piano_A4.wav │ ├── Piano_C5.wav │ ├── Snare_D2.wav │ └── Guitar_D_extended.wav └── HandelExport.js ├── dist ├── index.html └── HandelExport.js.LICENSE.txt ├── example.js ├── Examples ├── generative-example.txt ├── note_shifting_example.txt ├── droning_example.txt ├── a-thinker.txt └── around_and_around_example.txt ├── webpack.config.js ├── package.json ├── Contributing.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /src/Sounds/HiHat_G.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddj231/Handel/HEAD/src/Sounds/HiHat_G.wav -------------------------------------------------------------------------------- /src/Sounds/Kick_C.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddj231/Handel/HEAD/src/Sounds/Kick_C.wav -------------------------------------------------------------------------------- /src/Sounds/Piano_A4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddj231/Handel/HEAD/src/Sounds/Piano_A4.wav -------------------------------------------------------------------------------- /src/Sounds/Piano_C5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddj231/Handel/HEAD/src/Sounds/Piano_C5.wav -------------------------------------------------------------------------------- /src/Sounds/Snare_D2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddj231/Handel/HEAD/src/Sounds/Snare_D2.wav -------------------------------------------------------------------------------- /src/Sounds/Guitar_D_extended.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddj231/Handel/HEAD/src/Sounds/Guitar_D_extended.wav -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | function clicked(){ 2 | RunHandel(` 3 | start 4 | load hello as sampler 5 | finish 6 | `) 7 | } 8 | 9 | document.addEventListener("click", clicked); -------------------------------------------------------------------------------- /dist/HandelExport.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * Tone.js 3 | * @author Yotam Mann 4 | * @license http://opensource.org/licenses/MIT MIT License 5 | * @copyright 2014-2019 Yotam Mann 6 | */ 7 | -------------------------------------------------------------------------------- /Examples/generative-example.txt: -------------------------------------------------------------------------------- 1 | start 2 | save cmajor = C2, E2, G2, B2 3 | 4 | chunk example 5 | block 6 | save note = choose 1 from cmajor 7 | play note for 2b 8 | endblock loop for 100 9 | endchunk 10 | 11 | run example with sound piano 12 | finish -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/HandelExport.js', 5 | output: { 6 | filename: 'HandelExport.js', 7 | path: path.resolve(__dirname, 'dist'), 8 | library: "Handel", 9 | libraryTarget: "umd", 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.wav$/, 15 | loader: 'url-loader', 16 | options: { 17 | limit: Infinity 18 | } 19 | } 20 | ] 21 | } 22 | }; -------------------------------------------------------------------------------- /Examples/note_shifting_example.txt: -------------------------------------------------------------------------------- 1 | start 2 | chunk majorscale using startnote 3 | save mynote = startnote 4 | 5 | block 6 | play mynote for 1b 7 | update mynote rshift 2 8 | endblock loop for 3 9 | 10 | update mynote lshift 1 11 | 12 | block 13 | play mynote for 1b 14 | update mynote rshift 2 15 | endblock loop for 4 16 | 17 | update mynote lshift 1 18 | play mynote for 1b 19 | 20 | endchunk 21 | 22 | save startnote = Bb2 23 | run majorscale using startnote with sound piano 24 | finish -------------------------------------------------------------------------------- /Examples/droning_example.txt: -------------------------------------------------------------------------------- 1 | start 2 | chunk mysoundtrack using chordplayable, noteplayable 3 | play chordplayable 4 | rest for 1b 5 | play noteplayable 6 | endchunk 7 | 8 | chunk somehats 9 | play E3 for 1b 10 | endchunk 11 | 12 | save chordplayable = E3, Gb3, A#3 for 2b 13 | save noteplayable = A3 for 1b 14 | 15 | run mysoundtrack 16 | using chordplayable, noteplayable 17 | with sound synth, loop for 20, bpm 120 18 | 19 | run mysoundtrack 20 | using chordplayable, noteplayable 21 | with sound casio, loop for 20, bpm 120 22 | 23 | run somehats with sound hihat, bpm 240, loop for 300 24 | 25 | finish -------------------------------------------------------------------------------- /Examples/a-thinker.txt: -------------------------------------------------------------------------------- 1 | start 2 | chunk main 3 | play D3, F3, G3, B3 for 1b 4 | play F3, A3, C3, E3 for 1b 5 | play C3, E3, G3, B3 for 1b 6 | play D4, F3, G4, B3 for 1b 7 | play F4, G3, B4, D3 for 1b 8 | play D4, F3, G4, B3 for 1b 9 | play F4, G3, B4, D3 for 1b 10 | play D4, F3, G4, B3 for 1b 11 | endchunk 12 | 13 | chunk kickdrums 14 | play E1 for 1b 15 | rest for 3b 16 | endchunk 17 | 18 | chunk hats 19 | play E4 for 1b 20 | endchunk 21 | 22 | run main with sound piano, loop for 10 23 | run kickdrums with sound kick, loop for 20 24 | run hats with bpm 280, sound hihat, loop for 200 25 | 26 | finish -------------------------------------------------------------------------------- /Examples/around_and_around_example.txt: -------------------------------------------------------------------------------- 1 | start 2 | chunk mykeys 3 | play E3, G3, A3, C3 for 1b 4 | rest for 1b 5 | play G3, B3, D3, B3 for 1b 6 | rest for 1b 7 | play F3, A3, C3, E3 for 1b 8 | rest for 1b 9 | play B3, D3, F3, A3 for 1b 10 | rest for 1b 11 | endchunk 12 | 13 | chunk mykick 14 | rest for 1b 15 | play E2 for 1b 16 | endchunk 17 | 18 | chunk mysnare 19 | play E2 for 1b 20 | rest for 1b 21 | endchunk 22 | 23 | chunk myhats 24 | play E2 for 1b 25 | rest for 1b 26 | endchunk 27 | 28 | run mykeys with bpm 100, sound piano, loop for 300 29 | run mykick with bpm 100, sound kick, loop for 300 30 | run myhats with sound hihat, bpm 300, loop for 300 31 | run mysnare with sound snare, bpm 100, loop for 300 32 | 33 | finish -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "handel-pl", 3 | "version": "0.8.23", 4 | "description": "", 5 | "main": "dist/HandelExport.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/ddj231/Handel.git" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/ddj231/Handel/issues" 22 | }, 23 | "homepage": "https://github.com/ddj231/Handel#readme", 24 | "devDependencies": { 25 | "file-loader": "^6.2.0", 26 | "url-loader": "^4.1.1", 27 | "webpack": "^5.12.1", 28 | "webpack-cli": "^4.3.1" 29 | }, 30 | "dependencies": { 31 | "@tonejs/midi": "^2.0.25", 32 | "tone": "^14.7.77" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing to Handel. There are several areas to contribute to. 4 | 5 | # Bug fixes 6 | 7 | For bug fixes open an issue describing the bug. 8 | 9 | To add your fix, describe your proposed fix and make a pull request. 10 | 11 | # New language features 12 | 13 | ## Proposing language features 14 | 15 | To propose a new language feature, open an issue describing the feature to be added. Be as detailed as possible, perhaps including examples of the syntax, etc. 16 | 17 | ## Adding language features 18 | 19 | Before beginning work on a new language feature. Propose the issue (as above) -- and state that you would like to implement or partially implement the feature. 20 | 21 | Wait for feedback on the language feature, and whether it would make sense given the scope of Handel, before implementing. Then implement, and make a pull request. 22 | 23 | This is to limit rejected pull requests, so your time is not wasted. 24 | 25 | # Tutorials 26 | 27 | Creating tutorials on any platform is always welcome. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daye Jack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Handel 2 | 3 | Handel is a procedural programming language for writting songs in browser. 4 | 5 | The Handel Interpreter interprets Handel programs and plays compositions in browser, thanks to [Tone.js](https://tonejs.github.io/). 6 | 7 | Try the Handel Web Editor here: [Handel Web Editor](https://ddj231.github.io/Handel-Web-Editor/) 8 | 9 | See the language documentation (including latest features): [Handel Documentation](https://ddj231.github.io/Handel-Documentation/) 10 | 11 | Join the [Handel forum](https://groups.google.com/g/handel-pl) to ask questions and showcase compositions. Also check out the [Contributing](./Contributing.md) guidelines. 12 | 13 | *soli deo gloria* 14 | 15 | # Installation 16 | 17 | Add the below to your html document: 18 | ``` 19 | 20 | ``` 21 | 22 | You're all set! 23 | 24 | **Alternatively** 25 | 26 | Install Handel with the following: 27 | 28 | ``` 29 | npm i handel-pl 30 | ``` 31 | 32 | And import Handel with the following: 33 | 34 | ``` 35 | import * as Handel from 'handel-pl'; 36 | ``` 37 | 38 | 39 | # Usage 40 | 41 | ## Example Handel Snippet 42 | 43 | ``` 44 | start 45 | chunk example 46 | play E3, C3, G3 for 2b 47 | endchunk 48 | run example with sound piano, loop for 5 49 | finish 50 | ``` 51 | 52 | See the Examples folder [here](./Examples/) for example Handel programs and inspiration. 53 | 54 | ## Example Using Handel In Browser 55 | 56 | ``` 57 | function clicked(){ 58 | Handel.RunHandel(` 59 | start 60 | chunk example using somePlayable 61 | play somePlayable 62 | rest for 1b 63 | endchunk 64 | save myPlayable = Eb3 for 1b 65 | run example using myPlayable with sound piano, loop for 5 66 | finish 67 | `) 68 | } 69 | document.addEventListener("click", clicked); 70 | ``` 71 | 72 | Note that you pass the Handel code into the **RunHandel** function ```Handel.RunHandel(someHandelCode)```. 73 | 74 | To compile to midi, pass a config object to the RunHandel function with **outputMidi** set to true. 75 | 76 | ``` 77 | const config = {outputMidi: true}; 78 | Handel.RunHandel(`start play E4 for 1b finish`, config); 79 | ``` 80 | 81 | Additionally, you can use the **StopHandel** function to stop a running Handel program. ```Handel.StopHandel()``` 82 | 83 | 84 | # Getting started 85 | 86 | Handel programs are contained within the **start** and **finish** keywords. Below is a complete Handel program: 87 | 88 | ``` 89 | start 90 | play E4 for 1b 91 | finish 92 | ``` 93 | 94 | The program above only plays 1 note. But it's a start! 95 | 96 | 97 | # Let's play something 98 | 99 | You can be play notes and chords using the play command. Below is an example program that plays a note, then a chord: 100 | 101 | ``` 102 | start 103 | play C#3 for 1b 104 | play E3, G3, A4 for 1b 105 | finish 106 | ``` 107 | 108 | Note the syntax above. A **play** command begins with the **play** keyword, then a note or chord (a list of notes separated by commas) follows. 109 | 110 | Lastly play commands need a duration. The play commands above end with 'for 1b'. This states how long the particular note or notelist (chord) should be held. 111 | 112 | Phew! We're getting somewhere. 113 | 114 | 115 | # Let's rest 116 | 117 | Similar to the play command, a rest can played using the rest command. Below is an example program that rests for 1 beat then plays a note for 2 beats. 118 | 119 | ``` 120 | start 121 | rest for 1b 122 | play G5 for 2b 123 | finish 124 | ``` 125 | 126 | 127 | # But are there Variables? 128 | 129 | tl;dr 130 | Here is a code snippet showing variables in Handel 131 | 132 | ``` 133 | start 134 | save mynotelist = Cb3, D3 135 | save myduration = for 1b 136 | save myplayable = E4, F4, G3 for 3b 137 | 138 | save myotherplayable = mynotelist for myduration 139 | 140 | play myplayable 141 | rest myduration 142 | play myotherplayable 143 | finish 144 | ``` 145 | 146 | You can declare Variables in Handel. Variables store three builtin types in Handel: Notelists, Notegroups, Durations, Playables. 147 | 148 | A **Digit** is a positive or negative integer. 149 | 150 | A **Notelist** is a single note name, or a list of note names separated by commas. 151 | 152 | For example: 153 | 154 | ``` 155 | Bb3 156 | G#2, A2 157 | ``` 158 | 159 | A **Notegroup** is a group of notelists (or conceptually an array of notelists). Each notelist is separated by a vertical line. 160 | 161 | For example: 162 | 163 | ``` 164 | Bb3|G#2, A2 165 | ``` 166 | 167 | A **Duration** is the keyword **for** followed by a beat. 168 | 169 | Here are some example durations: 170 | 171 | ``` 172 | for 1b 173 | for 2b 174 | for 16b 175 | for 32b 176 | ``` 177 | 178 | Lastly, we've already seen **Playables** above. Playables are a note or notelist (chord) followed by a duration. 179 | Here are some example playables. 180 | 181 | ``` 182 | Bb3 for 1b 183 | D#6, E#6, G3 for 1b 184 | ``` 185 | 186 | *no promises that the above chord sounds pleasing to the ear :p* 187 | 188 | Finally variables! 189 | 190 | To store a notelist, playable or a duration use the **save** keyword, followed by a variable name, an equal sign and a notelist, playable, duration (or another variable which stores on of these values). 191 | 192 | Variable names must contain only lowercase letters, and no numbers. Variable names must also not be any of the reserved keywords in Handel. eSee the [Reserved Keywords](https://ddj231.github.io/Handel-Documentation/docs/keywords)). 193 | 194 | Below is an example program using variables. 195 | 196 | ``` 197 | start 198 | save mynote = E2 199 | save myplayablenote = mynote for 2b 200 | save myrest = for 2b 201 | 202 | play myplayablenote 203 | rest myrest 204 | play myplayablenote 205 | rest myrest 206 | finish 207 | ``` 208 | 209 | When saving variables, Handel now also provides expressions for **generating random numbers** and for **evaluating expressions**. 210 | 211 | To generate a random number when setting a variable, use the ```randint``` keyword, followed by a range ```start to end``` 212 | 213 | To evaluate an expression use the ```eval``` keyword followed by a mathematical expression (note division is integer division). 214 | 215 | Here is an example of the syntax: 216 | 217 | ``` 218 | save somerandomdigit = randint -5 to 5 219 | save someint = eval 5 * 5 / (1 + 1) % 6 220 | ``` 221 | 222 | OK! So far so good! 223 | 224 | # Variable Reassignment 225 | Handel now supports variable reassignment. Variables can be reassigned using the ```update``` keyword. 226 | 227 | For example: 228 | 229 | ``` 230 | save mynotelist = B3 231 | update mynotelist = Bb3 232 | ``` 233 | 234 | ## Reassignment and Shifting values 235 | 236 | All variables in Handel can be shifted using the ```rshift``` and ```lshift``` keywords. You can think of this as the equivalent of ```+=``` and ```-=``` respectively. 237 | 238 | For variables storing digits, this shifting is exactly the equivalent of addition and subtraction. 239 | 240 | For variables storing notelists and notegroups, shifting changes the notes, left or right by a number of semitones. 241 | 242 | The following example reassigns (or shifts) ```mynotelist`` left by one semitone. Then right by two semitones. 243 | 244 | ``` 245 | start 246 | save mynotelist = B3 247 | 248 | update mynotelist lshift 1 249 | play mynotelist for 1b 250 | 251 | save somenum = randint 1 to 5 252 | update mynotelist rshift somenum 253 | play mynotelist for 1b 254 | finish 255 | ``` 256 | 257 | Variables storing **Durations** and **Playables** can also be shifted. 258 | 259 | Shifting a duration increases or decreases its beat value. 260 | 261 | Shifting a playable (which contains a notelist) increases or decreass its notelist. 262 | 263 | For example: 264 | 265 | ``` 266 | start 267 | save duration = for 1b 268 | save playable = E3, G3 for 2b 269 | 270 | update duration rshift 1 271 | update playable lshift 2 272 | 273 | play playable 274 | rest duration 275 | play playable 276 | finish 277 | ``` 278 | 279 | # Blocks loops 280 | 281 | Handel supports block loops. Block loops begin with the **block** keyword and end with the **endblock** keyword and a ```loop for digit``` or ```loop while condition``` customization. 282 | 283 | Here is an example two block loops in Handel. 284 | ``` 285 | start 286 | block 287 | play C3, E3, G3 for 1b 288 | play D3, F3, A3 for 1b 289 | endblock loop for 2 290 | 291 | save note = C2 292 | block 293 | play note for 1b 294 | update note rshift 1 295 | endblock loop while note lessthan C3 296 | finish 297 | ``` 298 | 299 | Block loops are blocking (no pun intended), and should not be confused with Handel's procedures (chunks). 300 | 301 | More on procedures below. 302 | 303 | # Conditionals (if - else blocks) 304 | Though booleans are not built in types in Handel, Handel now supports conditonals. ```> < >= <= ==``` 305 | 306 | The syntax for an if - else block is as follows. 307 | 308 | ``` 309 | start 310 | if E4 > Cb3 then 311 | play E4 for 1b 312 | else 313 | play Cb3 for 1b 314 | endif 315 | 316 | save mydigit = 5 317 | if mydigit == 5 then 318 | play C2 for 5b 319 | endif 320 | finish 321 | ``` 322 | 323 | The above plays E4 for 1 beat. Note that else blocks are optional. 324 | 325 | # Procedures (I thought this was a procedural programming language?) 326 | 327 | Procedures in Handel are called chunks. You can conceptualize a **chunk** as a song track. When ran, 328 | chunks play at the same time as other run chunks and the global track. Chunks must begin with the **chunk** 329 | keyword and end with the **endchunk** keyword. 330 | 331 | Below is an example program with a kick drum and a piano, playing together. 332 | 333 | ``` 334 | start 335 | chunk backbeat using myplayable 336 | play myplayable 337 | endchunk 338 | 339 | chunk mykeys 340 | play E3, G3, A3 for 1b 341 | play G3, A2, C3 for 1b 342 | play F3, A3, C3 for 1b 343 | play D3, F2, A3 for 1b 344 | endchunk 345 | 346 | run mykeys with sound piano, loop for 2 347 | 348 | save myplayable = A1 for 1b 349 | 350 | run backbeat using myplayable with sound kick, loop for 8 351 | finish 352 | ``` 353 | 354 | Both the 'backbeat' chunk and the 'mykeys' chunk above play together (not one after the other). This 355 | behavior allows multitrack songs to be created with Handel. 356 | 357 | Note that each chunk has its own scope. 358 | 359 | 360 | # More on procedures (chunks) and their syntax 361 | 362 | 363 | ## Procedure declaration (creating chunks) 364 | 365 | As noted above you can create chunks with the **chunk** keyword. The name of the **chunk** (the chunk name) follows the keyword. 366 | 367 | This chunk name must be all lowercase letters, no numbers and cannot be one of Handel's reserved keywords. (See the [Reserved Keywords](https://ddj231.github.io/Handel-Documentation/docs/keywords)). 368 | 369 | After the chunk name, you can optionally add parameters. A list of comma separated parameters can follow the **using** keyword. 370 | 371 | Together you get the following: `chunk somechunkname using someparam, anotherparam` 372 | 373 | After the optional parameter list, you can add a body to the chunk. This is a function body (what you would like to happen when the chunk is ran). 374 | 375 | Lastly the chunk must be ended with the **endchunk** keyword. 376 | 377 | 378 | ## Running Procedures 379 | 380 | There are two ways to run a chunk in Handel. 381 | 382 | You can run a chunk using the **run** keyword. This conceptually creates a new song track, and plays the chunk synchronously with all running chunks. 383 | 384 | You can also run a chunk using the **call** keyword. This runs the chunk in place (in the songtrack the chunk is called in). 385 | 386 | The syntax for running a chunk is the **run** or **call** command followed by the name of the chunk. 387 | 388 | If the chunk has parameters, a you must use the ```using`` keyword followed by a matching number of comma separated arguments. 389 | 390 | Here is an example running two chunks. One chunk requires arguments the other does not. 391 | 392 | ``` 393 | start 394 | chunk playtwo using argone, argtwo 395 | play argone 396 | play argtwo 397 | endchunk 398 | 399 | chunk noargs 400 | play C4 for 1b 401 | call playtwo using E4 for 1b, Cb6 for 1b 402 | endchunk 403 | 404 | run noargs with sound piano 405 | finish 406 | ``` 407 | 408 | The run command is used to run noargs in its own conceptual song track. 409 | 410 | Within (```noargs```), 1 note plays, and the playtwo chunk is called in place. 411 | 412 | Note that saved variables (containing any built-in type in Handel), digits, playables, durations, can be used as arguments when running a chunk. 413 | 414 | OK! Now to configuring a run of a chunk. 415 | 416 | 417 | ## Configuring a run of a chunk 418 | 419 | You can configure a run of chunk by adding the **with** keyword and a comma separated list of customizations to the end of a run command. (customizations cannot be used with the call command as the chunk is being played in place) 420 | 421 | There are three main customizations: **bpm**, **sound**, and **loop**. 422 | 423 | You can use **bpm** keyword to set the bpm of a run of a chunk. 424 | 425 | For example ```bpm 120``` 426 | 427 | You can use the **sound** keyword to set the instrument of a run of a chunk. 428 | 429 | For example ```sound piano``` 430 | 431 | The current available sounds to choose from are: piano, synth, casio, kick, snare, hihat 432 | 433 | You can use the **loop** keyword to set the amount of times the run of a chunk shoud loop for. 434 | 435 | For example ```loop for 10``` 436 | 437 | All together you can configure a run of a chunk as follows: 438 | 439 | ``` 440 | start 441 | chunk withargs using somechord 442 | play somechord 443 | endchunk 444 | 445 | run withargs using E3, G3, F3 for 1b with bpm 100, loop for 8, sound piano 446 | finish 447 | ``` 448 | 449 | Above we've got a chord, played with a piano, looping 8 times, with a bpm of 100! 450 | 451 | (see the [Language Features reference](https://ddj231.github.io/Handel-Documentation/docs/language-features) for additional customizations) 452 | 453 | ## Custom Instruments 454 | Handel allows custom instruments to be loaded into Handel Programs. Instruments can be created and added to a run of a Handel program as follows. 455 | 456 | ``` 457 | let myinst = Handel.MakeInstrument({ 458 | A1: 'https://tonejs.github.io/audio/casio/A1.mp3', 459 | A2: 'https://tonejs.github.io/audio/casio/A2.mp3' 460 | }) 461 | let config = {} 462 | config.instruments = {funkyinst: myinst} 463 | Handel.RunHandel(` 464 | start 465 | load funkyinst as funky 466 | chunk example 467 | play E4 for 4b 468 | endchunk 469 | run example with sound funky 470 | finish 471 | `, config) 472 | ``` 473 | 474 | The ```MakeInstrument``` function wraps Tone.js's sampler constructor. It takes a urls object as its argument. This urls object, maps note names matched to their location (locally or not). One or more mappings can be used. 475 | 476 | After making an instrument above, we add it to our config object and run the Handel program with that config. 477 | 478 | Within the Handel program we load the instrument as follows: ``` 479 | load configInstrumentName as nameOfInstrumentWithinHandel``` 480 | 481 | **Note**: this feature makes your Handel program less portable but gives you the freedom of using arbitrary instruments in your Handel program. 482 | 483 | # Inspiration 484 | 485 | [Sonic Pi](https://sonic-pi.net) 486 | 487 | [Tone.js](https://tonejs.github.io) 488 | -------------------------------------------------------------------------------- /src/HandelExport.js: -------------------------------------------------------------------------------- 1 | import * as Tone from 'tone'; 2 | import { Midi } from '@tonejs/midi' 3 | 4 | import pianoA4 from './Sounds/Piano_A4.wav' 5 | import pianoC5 from './Sounds/Piano_C5.wav' 6 | import kickC from './Sounds/Kick_C.wav' 7 | import guitarD from './Sounds/Guitar_D_extended.wav' 8 | import hihatG from './Sounds/HiHat_G.wav' 9 | import snareD from './Sounds/Snare_D2.wav' 10 | 11 | class SampleMangager { 12 | constructor(){ 13 | this.samplers = []; 14 | } 15 | cleanup(){ 16 | for(const sampler of this.samplers){ 17 | if(sampler){ 18 | sampler.dispose(); 19 | } 20 | } 21 | this.samplers = []; 22 | } 23 | } 24 | const sampleManager = new SampleMangager(); 25 | 26 | export const Handel = (function () { 27 | console.log("%c Handel v0.8.23", "background: crimson; color: #fff; padding: 2px;"); 28 | class FMSynth { 29 | constructor() { 30 | this.synth = new Tone.PolySynth({ 31 | voice: Tone.FMSynth, 32 | envelope: { 33 | attack: 0.001, 34 | decay: 0.2, 35 | sustain: 0.002, 36 | release: 1, 37 | } 38 | }).toDestination(); 39 | this.synth.volume.value = -12; 40 | } 41 | } 42 | 43 | class Snare { 44 | constructor() { 45 | this.synth = new Tone.Sampler({ 46 | urls: { 47 | D2: snareD, 48 | }, 49 | //baseUrl: baseUrl, 50 | }).toDestination(); 51 | this.synth.volume.value = -3; 52 | sampleManager.samplers.push(this.synth); 53 | } 54 | } 55 | class Piano { 56 | constructor() { 57 | this.synth = new Tone.Sampler({ 58 | urls: { 59 | C5: pianoC5, 60 | A4: pianoA4, 61 | }, 62 | //baseUrl: baseUrl, 63 | }).toDestination(); 64 | sampleManager.samplers.push(this.synth); 65 | } 66 | } 67 | 68 | class Guitar { 69 | constructor() { 70 | this.synth = new Tone.Sampler({ 71 | urls: { 72 | D3: guitarD, 73 | }, 74 | //baseUrl: baseUrl, 75 | }).toDestination(); 76 | sampleManager.samplers.push(this.synth); 77 | } 78 | } 79 | 80 | class Kick { 81 | constructor() { 82 | this.synth = new Tone.Sampler({ 83 | urls: { 84 | C1: kickC, 85 | }, 86 | }).toDestination(); 87 | sampleManager.samplers.push(this.synth); 88 | } 89 | } 90 | 91 | class HiHat { 92 | constructor() { 93 | this.synth = new Tone.Sampler({ 94 | urls: { 95 | G3: hihatG, 96 | }, 97 | //baseUrl: baseUrl, 98 | }).toDestination(); 99 | sampleManager.samplers.push(this.synth); 100 | } 101 | } 102 | 103 | class Casio { 104 | constructor() { 105 | this.synth = new Tone.Sampler({ 106 | urls: { 107 | A1: "A1.mp3", 108 | A2: "A2.mp3", 109 | }, 110 | baseUrl: "https://tonejs.github.io/audio/casio/", 111 | }).toDestination(); 112 | sampleManager.samplers.push(this.synth); 113 | } 114 | } 115 | 116 | class Composition { 117 | constructor(synth, bpm, midiOption) { 118 | this.synth = new Tone.PolySynth({ voice: synth }); 119 | this.bpm = bpm; 120 | this.playEvents = []; 121 | this.currentTime = 0; 122 | this.midiTime = 0; 123 | this.startTime = 0; 124 | this.loopTimes = 1; 125 | this.volume; 126 | this.pan; 127 | this.reverb; 128 | this.offset = 0.1 129 | // Create Part 130 | this.part = new Tone.Part((time, value) => { 131 | Tone.loaded().then(() =>{ 132 | this.synth.triggerAttackRelease(value.notes, value.length, Tone.Time(time) + this.offset); 133 | }); 134 | }) 135 | //each composition also represents a midi track 136 | if (midiOption.midi) { 137 | this.midi = midiOption.midi; 138 | this.track = midiOption.midi.addTrack(); 139 | this.track.name = midiOption.trackName; 140 | this.notes = []; 141 | } 142 | } 143 | 144 | secondsFromBPM(beats) { 145 | return beats / (Tone.Transport.bpm.value / 60); 146 | } 147 | 148 | holdFor(beats) { 149 | let convertedBeats = beats * (Tone.Transport.bpm.value / this.bpm) 150 | return convertedBeats / (Tone.Transport.bpm.value / 60); 151 | } 152 | 153 | configurePart(playEvents) { 154 | for (let playEvent of playEvents) { 155 | this.playEvents.push(playEvent); 156 | let length = this.secondsFromBPM(playEvent.numBeats); 157 | let holdFor = this.holdFor(playEvent.numBeats); 158 | for (let i = 0; i < playEvent.rep; i++) { 159 | if (playEvent.notes) { 160 | this.part.add({ notes: playEvent.notes, time: this.currentTime, length: holdFor }); 161 | this.addNotesToTrack(playEvent.notes, this.midiTime, holdFor, length) 162 | } 163 | this.currentTime += length; 164 | this.midiTime += holdFor; 165 | } 166 | } 167 | } 168 | 169 | addNotesToTrack(notes, time, holdFor, length) { 170 | if (!this.midi) { return } 171 | for (let note of notes) { 172 | this.track.addNote({ 173 | name: note, 174 | time: time, 175 | duration: holdFor 176 | }); 177 | } 178 | } 179 | 180 | configureMidiLoop(times) { 181 | if (!this.midi) { return } 182 | for (let i = 1; i < times; i++) { 183 | for (let playEvent of this.playEvents) { 184 | let holdFor = this.holdFor(playEvent.numBeats); 185 | for (let i = 0; i < playEvent.rep; i++) { 186 | if (playEvent.notes) { 187 | this.addNotesToTrack(playEvent.notes, this.midiTime, holdFor, length) 188 | } 189 | this.midiTime += holdFor; 190 | } 191 | } 192 | } 193 | } 194 | 195 | configureLoop(times) { 196 | this.part.loopStart = Tone.Time(this.startTime); 197 | this.part.loopEnd = Tone.Time(this.currentTime); 198 | this.part.loop = times; 199 | } 200 | 201 | play() { 202 | this.part.playbackRate = this.bpm / Tone.Transport.bpm.value 203 | this.configureLoop(this.loopTimes); 204 | this.configureMidiLoop(this.loopTimes); 205 | if (!isNaN(this.volume)) { 206 | this.synth.volume.value = this.volume; 207 | } 208 | else { 209 | this.synth.volume.value = 0; 210 | } 211 | 212 | const verbVal = !isNaN(this.reverb) ? this.reverb : 0.001; 213 | const reverb = new Tone.Reverb(verbVal); 214 | if (!isNaN(this.pan)) { 215 | const panner = new Tone.Panner(this.pan).toDestination(); 216 | this.synth.chain(panner, reverb, Tone.getContext().destination); 217 | } 218 | else { 219 | //const panner = new Tone.Panner(0).toDestination(); 220 | this.synth.chain(reverb, Tone.getContext().destination); 221 | } 222 | this.part.start(0.1); 223 | } 224 | 225 | } 226 | 227 | class PlayEvent { 228 | constructor(notes, length, numBeats, rep = 1) { 229 | this.length = length; 230 | this.notes = notes; 231 | this.numBeats = numBeats; 232 | this.rep = rep 233 | } 234 | } 235 | 236 | // Token types 237 | const [NOTE, BPM, SOUND, 238 | VOLUME, PAN, REVERB, LOOP, BLOCK, ENDBLOCK, INSTRUMENT, CHOOSE, FROM, AND, OR, 239 | BEAT, RANDINT, DIGIT, FOR, SEP, VERTICAL, CHUNK, 240 | ENDCHUNK, ID, START, FINISH, SAVE, EVAL, UPDATE, SHIFT, DOT, PLAY, 241 | REST, WITH, RUN,CALL, TO, WHILE, MUL, DIV, MOD, PLUS, MINUS, LPAREN, RPAREN, SELECT, 242 | IF, THEN, ELSE, ENDIF, LESS, GREAT, EQUAL, NOTEQUAL, GREATEQ, LESSEQ, LOAD, AS, ASSIGN, USING, EOF] = [ 243 | "NOTE", "BPM", "SOUND", "VOLUME", "PAN", "REVERB", "LOOP", "BLOCK", "ENDBLOCK", 244 | "INSTRUMENT", "CHOOSE", "FROM", "AND", "OR", 245 | "BEAT", "RANDINT", "DIGIT", "FOR", "SEP","VERTICAL", "CHUNK", 246 | "ENDCHUNK", "ID", "START", "FINISH", "SAVE", "EVAL", "UPDATE", "SHIFT", "DOT", "PLAY", 247 | "REST", "WITH", "RUN", "CALL","TO","WHILE", "MUL", "DIV", "MOD", "PLUS", "MINUS", "LPAREN", "RPAREN", 248 | "SELECT", "IF", "THEN", "ELSE", "ENDIF", "LESS", "GREAT", "EQUAL", "NOTEQUAL", "GREATEQ", "LESSEQ", 249 | "LOAD", "AS", "ASSIGN", "USING", "EOF"]; 250 | 251 | 252 | class Token { 253 | constructor(type, value, lineno) { 254 | this.type = type; 255 | this.value = value; 256 | this.lineno = lineno 257 | } 258 | } 259 | 260 | const RESERVED_KEYWORDS = { 261 | for: new Token(FOR, 'for'), 262 | chunk: new Token(CHUNK, 'chunk'), 263 | endchunk: new Token(ENDCHUNK, 'endchunk'), 264 | save: new Token(SAVE, 'save'), 265 | update: new Token(UPDATE, 'update'), 266 | play: new Token(PLAY, 'play'), 267 | rest: new Token(REST, 'rest'), 268 | using: new Token(USING, 'using'), 269 | start: new Token(START, 'start'), 270 | finish: new Token(FINISH, 'finish'), 271 | run: new Token(RUN, 'run'), 272 | with: new Token(WITH, 'with'), 273 | bpm: new Token(BPM, 'bpm'), 274 | loop: new Token(LOOP, 'loop'), 275 | sound: new Token(SOUND, 'sound'), 276 | casio: new Token(INSTRUMENT, 'casio'), 277 | kick: new Token(INSTRUMENT, 'kick'), 278 | snare: new Token(INSTRUMENT, 'snare'), 279 | synth: new Token(INSTRUMENT, 'synth'), 280 | piano: new Token(INSTRUMENT, 'piano'), 281 | hihat: new Token(INSTRUMENT, 'hihat'), 282 | guitar: new Token(INSTRUMENT, 'guitar'), 283 | block: new Token(BLOCK, 'BLOCK'), 284 | endblock: new Token(ENDBLOCK, 'ENDBLOCK'), 285 | volume: new Token(VOLUME, 'VOLUME'), 286 | pan: new Token(PAN, 'PAN'), 287 | reverb: new Token(REVERB, 'REVERB'), 288 | load: new Token(LOAD, 'LOAD'), 289 | as: new Token(AS, 'AS'), 290 | lshift: new Token(SHIFT, "lshift"), 291 | rshift: new Token(SHIFT, "rshift"), 292 | if: new Token(IF, "if"), 293 | then: new Token(THEN, "then"), 294 | else: new Token(ELSE, "else"), 295 | endif: new Token(ENDIF, "endif"), 296 | randint: new Token(RANDINT, "randint"), 297 | to: new Token(TO, "to"), 298 | while: new Token(WHILE, "while"), 299 | eval: new Token(EVAL, "eval"), 300 | choose: new Token(CHOOSE, "choose"), 301 | from: new Token(FROM, "from"), 302 | select: new Token(SELECT, "select"), 303 | call: new Token(CALL, "call"), 304 | and: new Token(AND, "and"), 305 | or: new Token(OR, "or"), 306 | } 307 | 308 | class HandelSymbol { 309 | constructor(name, type = null) { 310 | this.name = name; 311 | this.type = type; 312 | } 313 | } 314 | 315 | class BuiltInTypeSymbol extends HandelSymbol { 316 | constructor(name) { 317 | super(name); 318 | } 319 | } 320 | 321 | class VarSymbol extends HandelSymbol { 322 | constructor(name, type) { 323 | super(name, type) 324 | } 325 | } 326 | 327 | class ProcedureSymbol extends HandelSymbol { 328 | constructor(name, type, params = null) { 329 | super(name); 330 | this.params = []; 331 | this.statementList = null; 332 | } 333 | } 334 | 335 | class HandelSymbolTable { 336 | constructor(scopeName, scopeLevel, enclosingScope = null) { 337 | this.symbols = {}; 338 | this.scopeName = scopeName; 339 | this.scopeLevel = scopeLevel; 340 | this.enclosingScope = enclosingScope; 341 | this.initbuiltins(); 342 | } 343 | initbuiltins() { 344 | let duration = new BuiltInTypeSymbol('BEAT'); 345 | let playable = new BuiltInTypeSymbol('PLAYABLE'); 346 | let digit = new BuiltInTypeSymbol('DIGIT'); 347 | let notelist = new BuiltInTypeSymbol('NOTELIST'); 348 | let notegroup = new BuiltInTypeSymbol('NOTEGROUP'); 349 | let any = new BuiltInTypeSymbol('ANY'); 350 | this.define(duration); 351 | this.define(playable); 352 | this.define(any); 353 | this.define(digit); 354 | this.define(notelist); 355 | this.define(notegroup); 356 | } 357 | define(symbol) { 358 | symbol.scopeLevel = this.scopeLevel; 359 | this.symbols[symbol.name] = symbol; 360 | } 361 | lookup(name, currentScopeOnly = false) { 362 | let symbol = this.symbols[name]; 363 | if (symbol) { 364 | return symbol; 365 | } 366 | 367 | if (currentScopeOnly) { 368 | return null; 369 | } 370 | 371 | if (this.enclosingScope) { 372 | return this.enclosingScope.lookup(name); 373 | } 374 | } 375 | 376 | error(varName) { 377 | throw Error(`Symbol ${varName} not found`) 378 | } 379 | } 380 | 381 | 382 | class HandelLexer { 383 | constructor(text) { 384 | this.text = text; 385 | this.pos = 0; 386 | this.currentChar = this.text[this.pos]; 387 | this.lineno = 1; 388 | this.possibleChars = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; 389 | this.possibleNums = ['0', '1', '2', '3', '4', '5', '6', '7']; 390 | } 391 | 392 | error() { 393 | throw new Error("error analyzing input at line: " + this.lineno + "; invalid character: " + this.currentChar); 394 | } 395 | 396 | peek() { 397 | if (this.pos >= this.text.length - 1) { 398 | return null; 399 | } 400 | else { 401 | return this.text[this.pos + 1]; 402 | } 403 | } 404 | 405 | advance() { 406 | this.pos += 1; 407 | if (this.pos >= this.text.length) { 408 | this.currentChar = null; 409 | } 410 | else { 411 | this.currentChar = this.text[this.pos]; 412 | } 413 | } 414 | 415 | isSpace(ch) { 416 | if (ch === '\n') { 417 | this.lineno += 1; 418 | } 419 | return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r'; 420 | } 421 | 422 | isAlpha(ch) { 423 | let regex = /^[a-zA-Z]{1}$/ 424 | return regex.test(ch); 425 | } 426 | 427 | skipWhitespace() { 428 | while (this.currentChar !== null && this.isSpace(this.currentChar)) { 429 | this.advance(); 430 | } 431 | } 432 | 433 | id() { 434 | let result = ""; 435 | while (this.currentChar && this.isAlpha(this.currentChar)) { 436 | result += this.currentChar; 437 | this.advance(); 438 | } 439 | 440 | if (result !== "") { 441 | if (result in RESERVED_KEYWORDS) { 442 | let token = RESERVED_KEYWORDS[result]; 443 | token.lineno = this.lineno 444 | return token; 445 | //return RESERVED_KEYWORDS[result]; 446 | } 447 | return new Token(ID, result, this.lineno); 448 | } 449 | } 450 | 451 | skipComments(){ 452 | if(this.currentChar === "/"){ 453 | this.advance(); 454 | while(this.currentChar !== "/"){ 455 | this.advance(); 456 | } 457 | this.advance(); 458 | } 459 | this.skipWhitespace(); 460 | } 461 | getNextToken() { 462 | // Lexical analyzer 463 | try { 464 | this.skipWhitespace(); 465 | this.skipComments(); 466 | 467 | if (this.pos >= this.text.length) { 468 | return new Token(EOF, null, this.lineno); 469 | } 470 | 471 | if (this.possibleChars.includes(this.currentChar)) { 472 | let note = this.currentChar; 473 | this.advance(); 474 | if (this.possibleNums.includes(this.currentChar)) { 475 | note += this.currentChar; 476 | this.advance(); 477 | return new Token(NOTE, note, this.lineno); 478 | } 479 | else if (this.currentChar === 'b' || this.currentChar === "#") { 480 | note += this.currentChar; 481 | this.advance(); 482 | if (this.possibleNums.includes(this.currentChar)) { 483 | note += this.currentChar; 484 | this.advance(); 485 | return new Token(NOTE, note, this.lineno); 486 | } 487 | } 488 | else { 489 | this.error(); 490 | } 491 | } 492 | 493 | if (this.currentChar === ',') { 494 | this.advance(); 495 | return new Token(SEP, 'sep', this.lineno); 496 | } 497 | 498 | if (this.currentChar === '>') { 499 | this.advance(); 500 | if(this.currentChar === '=') { 501 | this.advance(); 502 | return new Token(GREATEQ, 'greatereq', this.lineno) 503 | } 504 | return new Token(GREAT, 'greaterthan', this.lineno); 505 | } 506 | 507 | if (this.currentChar === '<') { 508 | this.advance(); 509 | if(this.currentChar === '=') { 510 | this.advance(); 511 | return new Token(GREATEQ, 'lesseq', this.lineno); 512 | } 513 | return new Token(LESS, 'lessthan', this.lineno); 514 | } 515 | 516 | if (this.currentChar === '=' && this.peek() === '=') { 517 | this.advance(); 518 | this.advance(); 519 | return new Token(EQUAL, 'equalto', this.lineno); 520 | } 521 | 522 | if (this.currentChar === '!' && this.peek() === '=') { 523 | this.advance(); 524 | this.advance(); 525 | return new Token(NOTEQUAL, 'notequal', this.lineno); 526 | } 527 | 528 | if (this.currentChar === '.') { 529 | this.advance(); 530 | return new Token(DOT, 'dot', this.lineno); 531 | } 532 | 533 | if (this.currentChar === '|') { 534 | this.advance(); 535 | return new Token(VERTICAL, '|', this.lineno); 536 | } 537 | 538 | if (this.currentChar === '=') { 539 | this.advance(); 540 | return new Token(ASSIGN, 'assign', this.lineno); 541 | } 542 | 543 | if (this.currentChar === '+') { 544 | this.advance(); 545 | return new Token(PLUS, '+', this.lineno); 546 | } 547 | 548 | if (this.currentChar === '*') { 549 | this.advance(); 550 | return new Token(MUL, '*', this.lineno); 551 | } 552 | 553 | if (this.currentChar === '/') { 554 | this.advance(); 555 | return new Token(DIV, '/', this.lineno); 556 | } 557 | 558 | if (this.currentChar === '%') { 559 | this.advance(); 560 | return new Token(MOD, '/', this.lineno); 561 | } 562 | 563 | if (this.currentChar === '(') { 564 | this.advance(); 565 | return new Token(LPAREN, '(', this.lineno); 566 | } 567 | 568 | if (this.currentChar === ')') { 569 | this.advance(); 570 | return new Token(RPAREN, ')', this.lineno); 571 | } 572 | 573 | let idResult = this.id(); 574 | if (idResult) { 575 | return idResult; 576 | } 577 | 578 | const value = this.currentChar; 579 | const parsed = Number.parseInt(value); 580 | if (!Number.isNaN(parsed) || this.currentChar == '-') { 581 | if(value === '-'){ 582 | this.advance(); 583 | let current = Number.parseInt(this.currentChar); 584 | if(Number.isNaN(current)){ 585 | return new Token(MINUS, '-', this.lineno); 586 | } 587 | } 588 | else{ 589 | this.advance(); 590 | } 591 | let digit = value; 592 | let current = Number.parseInt(this.currentChar); 593 | while (!Number.isNaN(current)) { 594 | digit += this.currentChar; 595 | this.advance(); 596 | current = Number.parseInt(this.currentChar); 597 | } 598 | if (this.currentChar === 'b') { 599 | this.advance(); 600 | return new Token(BEAT, digit, this.lineno); 601 | } 602 | return new Token(DIGIT, Number.parseInt(digit), this.lineno); 603 | } 604 | 605 | this.error(); 606 | } 607 | catch(ex){ 608 | throw ex; 609 | } 610 | } 611 | } 612 | 613 | class AST { 614 | constructor() { 615 | this.token = token; 616 | this.child = child; 617 | } 618 | } 619 | class BeatAST { 620 | constructor(token) { 621 | this.token = token; 622 | this.value = token.value; 623 | } 624 | } 625 | 626 | class LoadAST { 627 | constructor(token, instrumentName, localVarName) { 628 | this.token = token; 629 | this.instrumentName = instrumentName; 630 | this.localVarName = localVarName; 631 | } 632 | } 633 | 634 | 635 | 636 | class PlayAST { 637 | constructor(token, child = null, rep = null) { 638 | this.token = token; 639 | this.value = token.value; 640 | this.child = child; 641 | this.rep = rep; 642 | } 643 | } 644 | 645 | class AssignAST { 646 | constructor(token, left, right) { 647 | this.token = token; 648 | this.value = token.value; 649 | this.left = left; 650 | this.right = right; 651 | } 652 | } 653 | 654 | class UpdateAST { 655 | constructor(token, left, right, isShift) { 656 | this.token = token; 657 | this.value = token.value; 658 | this.left = left; 659 | this.right = right; 660 | this.varNode = left; 661 | this.exprNode = right; 662 | this.isShift = isShift; 663 | } 664 | } 665 | 666 | class DigitAST { 667 | constructor(token){ 668 | this.token = token; 669 | this.value = token.value; 670 | } 671 | } 672 | 673 | class ShiftAST { 674 | constructor(token, shiftNode) { 675 | this.token = token; 676 | this.value = token.value; 677 | this.shiftNode = shiftNode; 678 | } 679 | } 680 | 681 | class StatementListAST { 682 | constructor() { 683 | this.children = []; 684 | } 685 | } 686 | 687 | class ProgramAST { 688 | constructor(token, child) { 689 | this.token = token; 690 | this.value = token.value; 691 | this.child = child; 692 | } 693 | } 694 | 695 | class BlockLoopAST { 696 | constructor(token, statementList, loopTimes, whileCondition) { 697 | this.token = token; 698 | this.statementList = statementList; 699 | this.loopTimes = loopTimes; 700 | this.whileCondition = whileCondition; 701 | } 702 | } 703 | 704 | 705 | 706 | class ParameterListAST { 707 | constructor() { 708 | this.children = []; 709 | } 710 | } 711 | 712 | class ParameterAST { 713 | constructor(token, child) { 714 | this.token = token; 715 | this.value = token.value; 716 | this.child = child; 717 | } 718 | } 719 | 720 | class SectionDeclarationAST { 721 | constructor(token, parameterList, statementList) { 722 | this.token = token; 723 | this.value = this.token.value; 724 | this.parameterList = parameterList; 725 | this.statementList = statementList; 726 | } 727 | } 728 | 729 | class NoteGroupAST { 730 | constructor(token, notelists){ 731 | this.token = token; 732 | this.notelists = notelists; 733 | } 734 | } 735 | 736 | class BPMAST { 737 | constructor(token, bpm) { 738 | this.token = token; 739 | this.value = bpm; 740 | this.bpm = bpm; 741 | } 742 | } 743 | 744 | class LoopAST { 745 | constructor(token, loopTimes) { 746 | this.token = token; 747 | this.value = loopTimes; 748 | this.loopTimes = loopTimes; 749 | } 750 | } 751 | 752 | class BinOpAST { 753 | constructor(token, left, right){ 754 | this.token = token; 755 | this.op = token.value; 756 | this.left = left; 757 | this.right = right; 758 | } 759 | } 760 | 761 | class ConditionExpressionAST { 762 | constructor(token, left, right){ 763 | this.token = token; 764 | this.op = token.value; 765 | this.left = left; 766 | this.right = right; 767 | } 768 | } 769 | 770 | class VolumeAST { 771 | constructor(token, percentage) { 772 | this.token = token; 773 | this.value = percentage; 774 | this.percentage = percentage; 775 | } 776 | } 777 | 778 | class PanAST { 779 | constructor(token, panAmt) { 780 | this.token = token; 781 | this.value = panAmt; 782 | this.panAmt = panAmt; 783 | } 784 | } 785 | 786 | class ReverbAST { 787 | constructor(token, reverbAmt) { 788 | this.token = token; 789 | this.value = reverbAmt; 790 | this.reverbAmt = reverbAmt; 791 | } 792 | } 793 | 794 | class RepAST { 795 | constructor(token, repTimes) { 796 | this.token = token; 797 | this.value = repTimes; 798 | this.repTimes = repTimes; 799 | } 800 | } 801 | 802 | class InstrumentAST { 803 | constructor(token, instrument) { 804 | this.token = token; 805 | this.value = instrument; 806 | this.instrument = instrument; 807 | } 808 | } 809 | 810 | class ProcedureCallAST { 811 | constructor(token, actualParams, customizationList) { 812 | this.token = token; 813 | this.value = this.token.value; 814 | this.actualParams = actualParams; 815 | this.customizationList = customizationList; 816 | this.procSymbol = null; 817 | } 818 | } 819 | 820 | class SyncProcedureCallAST { 821 | constructor(token, actualParams) { 822 | this.token = token; 823 | this.value = this.token.value; 824 | this.actualParams = actualParams; 825 | this.procSymbol = null; 826 | } 827 | } 828 | 829 | 830 | class RestAST { 831 | constructor(token, child = null) { 832 | this.token = token; 833 | this.value = token.value; 834 | this.child = child; 835 | } 836 | } 837 | 838 | class NoteAST { 839 | constructor(token, child = null) { 840 | this.token = token; 841 | this.value = token.value; 842 | this.child = child; 843 | } 844 | } 845 | 846 | class BooleanAST { 847 | constructor(token){ 848 | this.token = token; 849 | this.value = token.value; 850 | } 851 | } 852 | 853 | class ComparisonAST { 854 | constructor(token){ 855 | this.token = token; 856 | this.value = token.value; 857 | } 858 | } 859 | 860 | class ConditionalStatementAST { 861 | constructor(token, condition, ifStatementList, elseStatementList){ 862 | this.token = token; 863 | this.value = token.value; 864 | this.condition = condition; 865 | this.ifStatementList = ifStatementList; 866 | this.elseStatementList = elseStatementList; 867 | } 868 | } 869 | 870 | class ConditionAST { 871 | constructor(token, operator, left, right){ 872 | this.token = token; 873 | this.operator = operator; 874 | this.left = left; 875 | this.right = right; 876 | } 877 | } 878 | 879 | class IdAST { 880 | constructor(token) { 881 | this.token = token; 882 | this.value = token.value; 883 | } 884 | } 885 | 886 | class ChooseAST { 887 | constructor(token, digitNode, notesNode){ 888 | this.token = token; 889 | this.digitNode = digitNode; 890 | this.notesNode = notesNode; 891 | } 892 | } 893 | 894 | class SelectAST { 895 | constructor(token, digitNode, notesNode){ 896 | this.token = token; 897 | this.digitNode = digitNode; 898 | this.notesNode = notesNode; 899 | } 900 | } 901 | 902 | 903 | class RandAST { 904 | constructor(token, start, end){ 905 | this.token = token; 906 | this.start = start; 907 | this.end = end; 908 | } 909 | } 910 | 911 | class ForAST { 912 | constructor(token, left, right) { 913 | this.token = token; 914 | this.left = left; 915 | this.right = right; 916 | } 917 | } 918 | 919 | class HandelParser { 920 | constructor(lexer) { 921 | this.lexer = lexer; 922 | this.currentToken = this.lexer.getNextToken(); 923 | } 924 | 925 | error() { 926 | throw new Error(`error parsing input at line ${this.currentToken.lineno}`); 927 | } 928 | 929 | eat(type) { 930 | if (this.currentToken.type === type) { 931 | this.currentToken = this.lexer.getNextToken(); 932 | } 933 | else { 934 | this.error(); 935 | } 936 | } 937 | 938 | play() { 939 | let token = this.currentToken; 940 | this.eat(PLAY); 941 | let child = this.expr(); 942 | let rep; 943 | if (this.currentToken.type === LOOP) { 944 | rep = this.rep(); 945 | } 946 | return new PlayAST(token, child, rep); 947 | } 948 | 949 | rep() { 950 | let token = this.currentToken; 951 | this.eat(LOOP); 952 | this.eat(FOR); 953 | let digit = this.currentToken; 954 | this.eat(DIGIT); 955 | return new RepAST(token, digit.value); 956 | } 957 | 958 | rest() { 959 | let restToken = this.currentToken; 960 | this.eat(REST); 961 | let beat; 962 | if (this.currentToken.type === FOR) { 963 | let forToken = this.currentToken; 964 | this.for(); 965 | beat = this.beat(); 966 | let forNode = new ForAST(forToken, beat, null); 967 | return new RestAST(restToken, forNode); 968 | } 969 | else if (this.currentToken.type === ID) { 970 | let varToken = this.id(); 971 | //beat = this.globalVariables[varName]; 972 | let idNode = new IdAST(varToken); 973 | //if(!this.currentScope.lookup(varName)){ this.currentScope.error(varName) } 974 | return new RestAST(restToken, idNode); 975 | } 976 | else { 977 | this.error(); 978 | } 979 | } 980 | 981 | chunk() { 982 | this.eat(CHUNK); 983 | } 984 | 985 | endchunk() { 986 | this.eat(ENDCHUNK); 987 | } 988 | 989 | using() { 990 | this.eat(USING); 991 | } 992 | 993 | program() { 994 | let startToken = this.currentToken; 995 | this.eat(START); 996 | let statementList; 997 | if (this.currentToken && 998 | (this.currentToken.type === CHUNK || 999 | this.currentToken.type === PLAY || 1000 | this.currentToken.type === REST || 1001 | this.currentToken.type === RUN || 1002 | this.currentToken.type === CALL|| 1003 | this.currentToken.type === LOAD || 1004 | this.currentToken.type === BLOCK || 1005 | this.currentToken.type === UPDATE || 1006 | this.currentToken.type === IF || 1007 | this.currentToken.type === SAVE)) { 1008 | try { 1009 | statementList = this.statementList(); 1010 | } 1011 | catch (e) { 1012 | throw e; 1013 | } 1014 | } 1015 | else { 1016 | this.error(); 1017 | } 1018 | let programNode = new ProgramAST(startToken, statementList); 1019 | this.eat(FINISH); 1020 | return programNode; 1021 | } 1022 | 1023 | digitExpression(){ 1024 | try{ 1025 | if(this.currentToken.type === EVAL){ 1026 | this.eat(EVAL); 1027 | } 1028 | let node = this.term(); 1029 | while(this.currentToken.type === PLUS || this.currentToken.type === MINUS){ 1030 | let token = this.currentToken; 1031 | if(token.type === PLUS){ 1032 | this.eat(PLUS); 1033 | } 1034 | else if(token.type === MINUS){ 1035 | this.eat(MINUS); 1036 | } 1037 | node = new BinOpAST(token, node, this.term()); 1038 | } 1039 | return node; 1040 | } 1041 | catch(ex){ 1042 | throw ex; 1043 | } 1044 | } 1045 | 1046 | conditionExpression(){ 1047 | let token = this.currentToken; 1048 | let node = this.conditionFactor(); 1049 | while(this.currentToken.type === AND || this.currentToken.type === OR){ 1050 | let token = this.currentToken; 1051 | if(token.type === AND){ 1052 | this.eat(AND); 1053 | } 1054 | else if(token.type === OR){ 1055 | this.eat(OR); 1056 | } 1057 | node = new ConditionExpressionAST(token, node, this.conditionFactor()); 1058 | } 1059 | return node; 1060 | } 1061 | 1062 | conditionFactor(){ 1063 | let node; 1064 | if(this.currentToken.type === LPAREN){ 1065 | this.eat(LPAREN); 1066 | node = this.conditionExpression(); 1067 | this.eat(RPAREN); 1068 | } 1069 | else { 1070 | node = this.condition(); 1071 | } 1072 | return node; 1073 | } 1074 | 1075 | selectExpression(){ 1076 | try { 1077 | let token = this.currentToken; 1078 | this.eat(SELECT); 1079 | let digitNode; 1080 | if(this.currentToken.type === RANDINT){ 1081 | digitNode = this.randint(); 1082 | } 1083 | else{ 1084 | digitNode = this.digitExpression(); 1085 | } 1086 | this.eat(FROM); 1087 | let notesNode; 1088 | if(this.currentToken.type === ID){ 1089 | notesNode = new IdAST(this.id()); 1090 | } 1091 | else if(this.currentToken.type === NOTE){ 1092 | notesNode = this.noteList(); 1093 | } 1094 | else if(this.currentToken.type === VERTICAL){ 1095 | notesNode = this.noteGroup(); 1096 | } 1097 | return new SelectAST(token, digitNode, notesNode); 1098 | } 1099 | catch(ex){ 1100 | throw ex; 1101 | } 1102 | 1103 | } 1104 | 1105 | 1106 | chooseExpression(){ 1107 | try { 1108 | let token = this.currentToken; 1109 | this.eat(CHOOSE); 1110 | let digitNode; 1111 | if(this.currentToken.type === RANDINT){ 1112 | digitNode = this.randint(); 1113 | } 1114 | else{ 1115 | digitNode = this.digitExpression(); 1116 | } 1117 | this.eat(FROM); 1118 | let notesNode; 1119 | if(this.currentToken.type === ID){ 1120 | notesNode = new IdAST(this.id()); 1121 | } 1122 | else if(this.currentToken.type === NOTE){ 1123 | notesNode = this.noteList(); 1124 | } 1125 | else if(this.currentToken.type === VERTICAL){ 1126 | notesNode = this.noteGroup(); 1127 | } 1128 | return new ChooseAST(token, digitNode, notesNode); 1129 | } 1130 | catch(ex){ 1131 | throw ex; 1132 | } 1133 | } 1134 | 1135 | term(){ 1136 | try{ 1137 | let node = this.factor(); 1138 | while(this.currentToken.type === MUL || this.currentToken.type === DIV || this.currentToken.type === MOD){ 1139 | let token = this.currentToken; 1140 | if(token.type === MUL){ 1141 | this.eat(MUL); 1142 | } 1143 | else if(token.type === DIV){ 1144 | this.eat(DIV); 1145 | } 1146 | else if(token.type === MOD){ 1147 | this.eat(MOD); 1148 | } 1149 | node = new BinOpAST(token, node, this.factor()); 1150 | } 1151 | return node; 1152 | } 1153 | catch(ex){ 1154 | throw ex; 1155 | } 1156 | } 1157 | 1158 | factor(){ 1159 | try { 1160 | if(this.currentToken.type === DIGIT){ 1161 | return this.digit(); 1162 | } 1163 | else if(this.currentToken.type === ID){ 1164 | return new IdAST(this.id()); 1165 | } 1166 | else if(this.currentToken.type === LPAREN){ 1167 | this.eat(LPAREN); 1168 | let node = this.digitExpression(); 1169 | this.eat(RPAREN); 1170 | return node; 1171 | } 1172 | else { 1173 | this.error(); 1174 | } 1175 | } 1176 | catch(ex){ 1177 | throw ex; 1178 | } 1179 | } 1180 | 1181 | sectionDeclaration() { 1182 | this.chunk(); 1183 | let proc; 1184 | try { 1185 | proc = this.id(); 1186 | } 1187 | catch (ex) { 1188 | throw ex; 1189 | } 1190 | let parameterList; 1191 | let statementListNode; 1192 | if (this.currentToken.type === USING) { 1193 | try { 1194 | this.using(); 1195 | parameterList = this.parameterList(); 1196 | } 1197 | catch (ex) { 1198 | throw ex; 1199 | } 1200 | } 1201 | try { 1202 | statementListNode = this.statementList(); 1203 | } catch (ex) { 1204 | throw ex; 1205 | 1206 | } 1207 | let sectionNode = new SectionDeclarationAST(proc, parameterList, statementListNode); 1208 | //sectionNode.statementList = statementListNode; 1209 | this.endchunk(); 1210 | return sectionNode; 1211 | } 1212 | 1213 | parameterList() { 1214 | try { 1215 | let parameterListNode = new ParameterListAST(); 1216 | let paramToken = this.id(); 1217 | let parameter = new ParameterAST(paramToken); 1218 | parameterListNode.children.push(parameter); 1219 | while (this.currentToken.type && this.currentToken.type === SEP) { 1220 | this.sep(); 1221 | let paramToken = this.id(); 1222 | let parameter = new ParameterAST(paramToken); 1223 | parameterListNode.children.push(parameter); 1224 | } 1225 | return parameterListNode; 1226 | } 1227 | catch (ex) { 1228 | throw ex; 1229 | } 1230 | } 1231 | 1232 | argumentList() { 1233 | try { 1234 | let actualParams = []; 1235 | actualParams.push(this.expr()); 1236 | while (this.currentToken && this.currentToken.type === SEP) { 1237 | this.sep(); 1238 | actualParams.push(this.expr()); 1239 | } 1240 | return actualParams; 1241 | } 1242 | catch (ex) { 1243 | throw ex; 1244 | } 1245 | } 1246 | 1247 | noteGroup(){ 1248 | let token = this.currentToken; 1249 | this.eat(VERTICAL); 1250 | let notelists = []; 1251 | if(this.currentToken.type === ID) { 1252 | notelists.push(this.id()); 1253 | } 1254 | else { 1255 | notelists.push(this.noteList()); 1256 | } 1257 | while(this.currentToken.type === VERTICAL){ 1258 | this.eat(VERTICAL); 1259 | if(this.currentToken.type === ID) { 1260 | notelists.push(this.id()); 1261 | } 1262 | else { 1263 | notelists.push(this.noteList()); 1264 | } 1265 | } 1266 | return new NoteGroupAST(token, notelists); 1267 | } 1268 | 1269 | customization() { 1270 | try { 1271 | if (this.currentToken.type === BPM) { 1272 | let bpmToken = this.currentToken; 1273 | this.eat(BPM); 1274 | let digit = this.digitRepresenter(); 1275 | return new BPMAST(bpmToken, digit); 1276 | } 1277 | else if (this.currentToken.type === SOUND) { 1278 | let soundToken = this.currentToken; 1279 | this.eat(SOUND); 1280 | let instrument = this.currentToken.value; 1281 | if (this.currentToken.type === INSTRUMENT) { 1282 | this.eat(INSTRUMENT); 1283 | } 1284 | else { 1285 | this.eat(ID); 1286 | } 1287 | return new InstrumentAST(soundToken, instrument); 1288 | } 1289 | else if (this.currentToken.type === LOOP) { 1290 | let loopToken = this.currentToken; 1291 | this.eat(LOOP); 1292 | this.eat(FOR); 1293 | let digit = this.digitRepresenter(); 1294 | return new LoopAST(loopToken, digit); 1295 | } 1296 | else if (this.currentToken.type === VOLUME) { 1297 | let volumeToken = this.currentToken; 1298 | this.eat(VOLUME); 1299 | let digit = this.digitRepresenter(); 1300 | return new VolumeAST(volumeToken, digit); 1301 | } 1302 | else if (this.currentToken.type === PAN) { 1303 | let panToken = this.currentToken; 1304 | this.eat(PAN); 1305 | let digit = this.digitRepresenter(); 1306 | return new PanAST(panToken, digit); 1307 | } 1308 | else if (this.currentToken.type === REVERB) { 1309 | let reverbToken = this.currentToken; 1310 | this.eat(REVERB); 1311 | let digit = this.digitRepresenter(); 1312 | return new ReverbAST(reverbToken, digit); 1313 | } 1314 | else { 1315 | this.error(); 1316 | } 1317 | } 1318 | catch (ex) { 1319 | throw ex; 1320 | } 1321 | } 1322 | 1323 | digitRepresenter(){ 1324 | let digit; 1325 | if(this.currentToken.type === DIGIT){ 1326 | digit = this.digit(); 1327 | } 1328 | else if(this.currentToken.type === ID){ 1329 | digit = new IdAST(this.id()); 1330 | } 1331 | else if(this.currentToken.type === EVAL){ 1332 | digit = this.digitExpression(); 1333 | } 1334 | else if(this.currentToken.type === RANDINT){ 1335 | digit = this.randint(); 1336 | } 1337 | return digit; 1338 | } 1339 | 1340 | customizationList() { 1341 | try { 1342 | let customizations = []; 1343 | customizations.push(this.customization()); 1344 | while (this.currentToken && this.currentToken.type === SEP) { 1345 | this.eat(SEP); 1346 | customizations.push(this.customization()); 1347 | } 1348 | return customizations; 1349 | } 1350 | catch (ex) { 1351 | throw ex; 1352 | } 1353 | } 1354 | 1355 | boolean(){ 1356 | try{ 1357 | if(this.currentToken.type === TRUE || this.currentToken.type === FALSE){ 1358 | return new BooleanAST(this.currentToken); 1359 | } 1360 | else { 1361 | this.error(); 1362 | } 1363 | } 1364 | catch(ex){ 1365 | throw ex; 1366 | } 1367 | } 1368 | 1369 | comparison_operator(){ 1370 | try{ 1371 | let token = this.currentToken; 1372 | if(this.currentToken.type === LESS){ 1373 | this.eat(LESS); 1374 | } 1375 | if(this.currentToken.type === LESSEQ){ 1376 | this.eat(LESS); 1377 | } 1378 | else if(this.currentToken.type === GREAT){ 1379 | this.eat(GREAT); 1380 | } 1381 | else if(this.currentToken.type === GREATEQ){ 1382 | this.eat(GREATEQ); 1383 | } 1384 | else if(this.currentToken.type === EQUAL){ 1385 | this.eat(EQUAL); 1386 | } 1387 | else if(this.currentToken.type === NOTEQUAL){ 1388 | this.eat(NOTEQUAL); 1389 | } 1390 | return new ComparisonAST(token); 1391 | } 1392 | catch(ex){ 1393 | throw ex; 1394 | } 1395 | } 1396 | 1397 | condition(){ 1398 | try{ 1399 | let left; 1400 | let right; 1401 | 1402 | if(this.currentToken.type === EVAL || this.currentToken.type === DIGIT){ 1403 | left = this.digitExpression(); 1404 | } 1405 | else if(this.currentToken.type === RANDINT){ 1406 | left = this.randint(); 1407 | } 1408 | else if(this.currentToken.type === CHOOSE){ 1409 | left = this.chooseExpression(); 1410 | } 1411 | else if(this.currentToken.type === SELECT){ 1412 | left = this.selectExpression(); 1413 | } 1414 | else { 1415 | left = this.expr(); 1416 | } 1417 | 1418 | let token = this.currentToken; 1419 | let op = this.comparison_operator(); 1420 | 1421 | if(this.currentToken.type === EVAL || this.currentToken.type === DIGIT){ 1422 | right = this.digitExpression(); 1423 | } 1424 | else if(this.currentToken.type === RANDINT){ 1425 | right = this.randint(); 1426 | } 1427 | else if(this.currentToken.type === CHOOSE){ 1428 | right = this.chooseExpression(); 1429 | } 1430 | else if(this.currentToken.type === SELECT){ 1431 | right = this.selectExpression(); 1432 | } 1433 | else { 1434 | right = this.expr(); 1435 | } 1436 | return new ConditionAST(token, op, left, right); 1437 | } 1438 | catch(ex){ 1439 | throw ex; 1440 | } 1441 | } 1442 | 1443 | conditional_statement(){ 1444 | try { 1445 | let token = this.currentToken; 1446 | this.eat(IF); 1447 | let condition = this.conditionExpression(); 1448 | this.eat(THEN); 1449 | let ifStatementList = this.statementList(); 1450 | let elseStatementList; 1451 | if(this.currentToken.type === ELSE){ 1452 | this.eat(ELSE); 1453 | elseStatementList = this.statementList(); 1454 | } 1455 | this.eat(ENDIF); 1456 | return new ConditionalStatementAST(token, condition, ifStatementList, elseStatementList); 1457 | } 1458 | catch(ex){ 1459 | throw ex; 1460 | } 1461 | } 1462 | 1463 | blockLoop() { 1464 | try { 1465 | let blockToken = this.currentToken; 1466 | this.eat(BLOCK); 1467 | let statementList = this.statementList(); 1468 | this.eat(ENDBLOCK); 1469 | this.eat(LOOP); 1470 | let token; 1471 | let condition; 1472 | let node; 1473 | if(this.currentToken.type === FOR){ 1474 | this.eat(FOR); 1475 | token = this.currentToken; 1476 | if(this.currentToken.type === DIGIT || this.currentToken.type === EVAL){ 1477 | node = this.digitExpression(); 1478 | } 1479 | else if(this.currentToken.type === ID){ 1480 | node = new IdAST(token); 1481 | } 1482 | } 1483 | else if(this.currentToken.type === WHILE){ 1484 | this.eat(WHILE); 1485 | condition = this.conditionExpression(); 1486 | } 1487 | return new BlockLoopAST(blockToken, statementList, node, condition); 1488 | } 1489 | catch (ex) { 1490 | throw ex; 1491 | } 1492 | } 1493 | 1494 | syncProcedureCall(){ 1495 | try { 1496 | this.eat(CALL); 1497 | let procedureToken = this.currentToken; 1498 | procedureToken.category = "SYNC"; 1499 | this.eat(ID); 1500 | let actualParams = []; 1501 | if (this.currentToken.type === USING) { 1502 | this.eat(USING); 1503 | if (this.currentToken.type === FOR || this.currentToken.type === NOTE || this.currentToken.type === ID) { 1504 | actualParams = this.argumentList(); 1505 | } 1506 | } 1507 | return new SyncProcedureCallAST(procedureToken, actualParams); 1508 | } 1509 | catch(ex){ 1510 | throw ex; 1511 | } 1512 | } 1513 | 1514 | procedureCall() { 1515 | try { 1516 | this.eat(RUN); 1517 | let procedureToken = this.currentToken; 1518 | procedureToken.category = "ASYNC"; 1519 | this.eat(ID); 1520 | let actualParams = []; 1521 | if (this.currentToken.type === USING) { 1522 | this.eat(USING); 1523 | actualParams = this.argumentList(); 1524 | } 1525 | let customizationList = []; 1526 | if (this.currentToken.type === WITH) { 1527 | this.eat(WITH); 1528 | customizationList = this.customizationList(); 1529 | } 1530 | return new ProcedureCallAST(procedureToken, actualParams, customizationList); 1531 | } 1532 | catch (ex) { 1533 | throw ex; 1534 | } 1535 | } 1536 | 1537 | statementList() { 1538 | try { 1539 | let statementListNode = new StatementListAST(); 1540 | while (this.currentToken && 1541 | (this.currentToken.type === CHUNK || 1542 | this.currentToken.type === PLAY || 1543 | this.currentToken.type === REST || 1544 | this.currentToken.type === RUN || 1545 | this.currentToken.type === CALL|| 1546 | this.currentToken.type === LOAD || 1547 | this.currentToken.type === BLOCK || 1548 | this.currentToken.type === UPDATE || 1549 | this.currentToken.type === IF || 1550 | this.currentToken.type === SAVE) 1551 | ) { 1552 | if (this.currentToken.type === CHUNK) { 1553 | statementListNode.children.push(this.sectionDeclaration()); 1554 | } 1555 | else { 1556 | statementListNode.children.push(this.statement()); 1557 | } 1558 | } 1559 | return statementListNode; 1560 | } 1561 | catch (ex) { 1562 | throw ex; 1563 | } 1564 | } 1565 | 1566 | importInstrument() { 1567 | try { 1568 | let token = this.currentToken; 1569 | this.eat(LOAD); 1570 | let instrumentName = this.currentToken.value; 1571 | this.eat(ID); 1572 | this.eat(AS); 1573 | let localVarName = this.currentToken.value; 1574 | this.eat(ID); 1575 | return new LoadAST(token, instrumentName, localVarName); 1576 | } 1577 | catch (ex) { 1578 | throw ex; 1579 | } 1580 | } 1581 | 1582 | statement() { 1583 | try { 1584 | if (this.currentToken.type === PLAY) { 1585 | return this.play(); 1586 | } 1587 | else if (this.currentToken.type === REST) { 1588 | return this.rest(); 1589 | } 1590 | else if (this.currentToken.type === RUN) { 1591 | return this.procedureCall(); 1592 | } 1593 | else if (this.currentToken.type === CALL) { 1594 | return this.syncProcedureCall(); 1595 | } 1596 | else if (this.currentToken.type === BLOCK) { 1597 | return this.blockLoop(); 1598 | } 1599 | else if (this.currentToken.type === SAVE) { 1600 | //variable assignment 1601 | return this.save(); 1602 | } 1603 | else if (this.currentToken.type === UPDATE) { 1604 | //variable reassignment 1605 | return this.update(); 1606 | } 1607 | else if (this.currentToken.type === LOAD) { 1608 | return this.importInstrument(); 1609 | } 1610 | else if (this.currentToken.type === IF) { 1611 | return this.conditional_statement(); 1612 | } 1613 | else { 1614 | this.error(); 1615 | } 1616 | } 1617 | catch (ex) { 1618 | throw ex; 1619 | } 1620 | } 1621 | 1622 | randint(){ 1623 | try { 1624 | let token = this.currentToken; 1625 | this.eat(RANDINT); 1626 | let start; 1627 | let end; 1628 | 1629 | if(this.currentToken.type === EVAL || this.currentToken.type === DIGIT){ 1630 | start = this.digitExpression(); 1631 | } 1632 | else if(this.currentToken.type === RANDINT){ 1633 | start = this.randint(); 1634 | } 1635 | else { 1636 | start = this.expr(); 1637 | } 1638 | 1639 | 1640 | this.eat(TO); 1641 | 1642 | if(this.currentToken.type === EVAL || this.currentToken.type === DIGIT){ 1643 | end = this.digitExpression(); 1644 | } 1645 | else if(this.currentToken.type === EVAL){ 1646 | end = this.digitExpression(); 1647 | } 1648 | else if(this.currentToken.type === RANDINT){ 1649 | end = this.randint(); 1650 | } 1651 | else { 1652 | end= this.expr(); 1653 | } 1654 | return new RandAST(token, start, end); 1655 | } 1656 | catch(ex){ 1657 | throw ex; 1658 | } 1659 | } 1660 | 1661 | save() { 1662 | try { 1663 | this.eat(SAVE); 1664 | let varToken = this.currentToken; 1665 | let varNode = new IdAST(varToken); 1666 | this.eat(ID); 1667 | let assignToken = this.currentToken; 1668 | this.eat(ASSIGN); 1669 | if (this.currentToken.type === NOTE || this.currentToken.type === ID) { 1670 | let node = this.expr(); 1671 | return new AssignAST(assignToken, varNode, node); 1672 | } 1673 | else if (this.currentToken.type === FOR) { 1674 | this.for(); 1675 | let beat = this.beat(); 1676 | return new AssignAST(assignToken, varNode, beat); 1677 | } 1678 | else if(this.currentToken.type === RANDINT){ 1679 | let node = this.randint(); 1680 | return new AssignAST(assignToken, varNode, node); 1681 | } 1682 | else if(this.currentToken.type === DIGIT || this.currentToken.type === EVAL){ 1683 | let node = this.digitExpression(); 1684 | return new AssignAST(assignToken, varNode, node); 1685 | } 1686 | else if(this.currentToken.type === CHOOSE){ 1687 | let node = this.chooseExpression(); 1688 | return new AssignAST(assignToken, varNode, node); 1689 | } 1690 | else if(this.currentToken.type === SELECT){ 1691 | let node = this.selectExpression(); 1692 | return new AssignAST(assignToken, varNode, node); 1693 | } 1694 | else if(this.currentToken.type === VERTICAL){ 1695 | let node = this.noteGroup(); 1696 | return new AssignAST(assignToken, varNode, node); 1697 | } 1698 | } 1699 | catch (ex) { 1700 | throw ex; 1701 | } 1702 | } 1703 | 1704 | digit(){ 1705 | try { 1706 | let token = this.currentToken; 1707 | this.eat(DIGIT); 1708 | return new DigitAST(token); 1709 | } 1710 | catch (ex){ 1711 | throw ex; 1712 | } 1713 | } 1714 | 1715 | update() { 1716 | try { 1717 | let updateToken = this.currentToken; 1718 | this.eat(UPDATE); 1719 | let varNode = new IdAST(this.currentToken); 1720 | this.eat(ID); 1721 | if (this.currentToken.type === ASSIGN) { 1722 | this.eat(ASSIGN); 1723 | let exprNode; 1724 | if(this.currentToken.type === NOTE || this.currentToken.type === FOR 1725 | || this.currentToken.type === BEAT || this.currentToken.type === ID){ 1726 | exprNode = this.expr(); 1727 | } 1728 | else { 1729 | exprNode = this.digitExpression(); 1730 | } 1731 | return new UpdateAST(updateToken, varNode, exprNode, false); 1732 | } 1733 | else if (this.currentToken.type === SHIFT) { 1734 | let shiftNode = this.shift(); 1735 | return new UpdateAST(updateToken, varNode, shiftNode, true); 1736 | } 1737 | else { 1738 | this.error(); 1739 | } 1740 | } 1741 | catch (ex) { 1742 | throw ex; 1743 | } 1744 | } 1745 | 1746 | shift() { 1747 | try { 1748 | let shiftToken = this.currentToken; 1749 | this.eat(SHIFT); 1750 | let token = this.currentToken; 1751 | let node; 1752 | 1753 | if(token.type === ID){ 1754 | node = new IdAST(this.id()); 1755 | } 1756 | else if(token.type === RANDINT){ 1757 | node = this.randint(); 1758 | } 1759 | else if(token.type === DIGIT || token.type === EVAL){ 1760 | node = this.digitExpression(); 1761 | } 1762 | else { 1763 | this.error(); 1764 | } 1765 | return new ShiftAST(shiftToken, node); 1766 | } 1767 | catch (ex) { 1768 | throw ex; 1769 | } 1770 | } 1771 | 1772 | note() { 1773 | try { 1774 | const note = this.currentToken; 1775 | this.eat(NOTE); 1776 | return new NoteAST(note, null); 1777 | } 1778 | catch (ex) { 1779 | throw ex; 1780 | } 1781 | } 1782 | 1783 | noteList() { 1784 | try { 1785 | const notes = []; 1786 | let node = this.note(); 1787 | let root = node; 1788 | while (this.currentToken && this.currentToken.type === SEP) { 1789 | let sepToken = this.sep(); 1790 | let temp = this.note(); 1791 | node.child = temp; 1792 | node = temp; 1793 | } 1794 | return root; 1795 | } 1796 | catch (ex) { 1797 | throw ex; 1798 | } 1799 | } 1800 | 1801 | sep() { 1802 | try { 1803 | const sep = this.currentToken; 1804 | this.eat(SEP); 1805 | } 1806 | catch (ex) { 1807 | throw ex; 1808 | } 1809 | } 1810 | 1811 | for() { 1812 | try { 1813 | const op = this.currentToken; 1814 | this.eat(FOR); 1815 | return op; 1816 | } 1817 | catch (ex) { 1818 | throw ex; 1819 | } 1820 | } 1821 | 1822 | id() { 1823 | try { 1824 | let idToken = this.currentToken; 1825 | this.eat(ID); 1826 | return idToken; 1827 | } 1828 | catch (ex) { 1829 | throw ex; 1830 | } 1831 | } 1832 | 1833 | beat() { 1834 | try { 1835 | const beat = this.currentToken; 1836 | this.eat(BEAT); 1837 | return new BeatAST(beat); 1838 | } 1839 | catch (ex) { 1840 | throw ex; 1841 | } 1842 | } 1843 | 1844 | expr() { 1845 | try { 1846 | if (this.currentToken.type === NOTE || this.currentToken.type === CHOOSE || 1847 | this.currentToken.type === SELECT) { 1848 | let noteRoot; 1849 | if(this.currentToken.type === NOTE){ 1850 | noteRoot = this.noteList(); 1851 | } 1852 | else if(this.currentToken.type === CHOOSE){ 1853 | noteRoot = this.chooseExpression(); 1854 | } 1855 | else { 1856 | noteRoot = this.selectExpression(); 1857 | } 1858 | 1859 | if (this.currentToken.type === FOR) { 1860 | let op = this.for(); 1861 | let beat; 1862 | if(this.currentToken.type === BEAT){ 1863 | beat = this.beat(); 1864 | } 1865 | else if(this.currentToken.type == ID){ 1866 | beat = new IdAST(this.currentToken); 1867 | this.eat(ID); 1868 | } 1869 | let forNode = new ForAST(op, noteRoot, beat) 1870 | return forNode; 1871 | } 1872 | else { 1873 | return noteRoot; 1874 | } 1875 | } 1876 | else if (this.currentToken.type === ID) { 1877 | let varToken = this.id(); 1878 | if (this.currentToken.type === FOR) { 1879 | let op = this.for(); 1880 | let leftNode = new IdAST(varToken); 1881 | let rightNode; 1882 | if (this.currentToken.type === BEAT) { 1883 | rightNode = this.beat(); 1884 | } 1885 | else if (this.currentToken.type === ID) { 1886 | const leftVarToken = this.id(); 1887 | rightNode = new IdAST(leftVarToken); 1888 | } 1889 | let forNode = new ForAST(op, leftNode, rightNode); 1890 | return forNode; 1891 | } 1892 | return new IdAST(varToken); 1893 | } 1894 | else if (this.currentToken.type === FOR) { 1895 | let forToken = this.currentToken; 1896 | this.for(); 1897 | let beat = this.beat(); 1898 | //let forNode = new ForAST(forToken, beat, null); 1899 | return beat; 1900 | } 1901 | else if (this.currentToken.type === DIGIT || this.currentToken.type === EVAL) { 1902 | return this.digitExpression(); 1903 | } 1904 | else if (this.currentToken.type === VERTICAL) { 1905 | return this.noteGroup(); 1906 | } 1907 | } catch (ex) { 1908 | throw ex; 1909 | } 1910 | } 1911 | } 1912 | 1913 | const ARTYPES = { PROGRAM: "PROGRAM", PROCEDURE: "PROCEDURE", BLOCK: "BLOCK" } 1914 | class HandelActivationRecord { 1915 | constructor(name, type, nestingLevel) { 1916 | this.name = name; 1917 | this.type = type; 1918 | this.nestingLevel = nestingLevel; 1919 | this.members = {}; 1920 | this.enclosingRecord; 1921 | } 1922 | 1923 | setItem(key, value) { 1924 | this.members[key] = value; 1925 | } 1926 | 1927 | getItem(key, currentRecordOnly = false) { 1928 | if (this.members[key] !== null && this.members[key] !== undefined) { 1929 | return this.members[key]; 1930 | } 1931 | 1932 | if (currentRecordOnly) { 1933 | return this.members[key]; 1934 | } 1935 | 1936 | if (this.enclosingRecord) { 1937 | return this.enclosingRecord.getItem(key, currentRecordOnly); 1938 | } 1939 | } 1940 | 1941 | get(key, currentRecordOnly = false) { 1942 | return this.getItem(key, currentRecordOnly); 1943 | } 1944 | } 1945 | 1946 | 1947 | class HandelCallStack { 1948 | constructor() { 1949 | this.records = []; 1950 | } 1951 | 1952 | push(val) { 1953 | this.records.push(val); 1954 | } 1955 | pop() { 1956 | this.records.pop(); 1957 | } 1958 | 1959 | peek() { 1960 | if (this.records.length <= 0) { 1961 | return null; 1962 | } 1963 | return this.records[this.records.length - 1]; 1964 | } 1965 | } 1966 | 1967 | class HandelInterpreter { 1968 | constructor(parser, config, midi) { 1969 | this.parser = parser; 1970 | this.beatToValue = { 1971 | 1: '4n', 1972 | 2: '2n', 1973 | 3: '2n.', 1974 | 4: '1m' 1975 | } 1976 | this.currentComposition; 1977 | this.config = config; 1978 | this.midi = midi; 1979 | this.callStack = new HandelCallStack(); 1980 | this.totalSamples = 0; 1981 | this.samplesCount = 0; 1982 | this.increment = this.increment.bind(this); 1983 | } 1984 | 1985 | exportMidi() { 1986 | let a = document.createElement("a"); 1987 | let file = new Blob([this.midi.toArray()], { type: 'audio/midi' }); 1988 | a.href = URL.createObjectURL(file); 1989 | a.download = "my-midi.mid" 1990 | a.click(); 1991 | URL.revokeObjectURL(a.href); 1992 | } 1993 | 1994 | increment(){ 1995 | this.samplesCount += 1; 1996 | } 1997 | 1998 | visitProgram(node) { 1999 | try { 2000 | Tone.Transport.bpm.value = 1000 2001 | let ar = new HandelActivationRecord('program', ARTYPES.PROGRAM, 1); 2002 | ar.enclosingRecord = null; 2003 | this.currentComposition = new Composition(Tone.FMSynth, 140, 2004 | { trackName: 'global', midi: this.midi }); 2005 | this.currentComposition.enclosingComposition = null; 2006 | this.callStack.push(ar); 2007 | this.visitStatementList(node.child); 2008 | this.currentComposition.play(); 2009 | this.callStack.pop(); 2010 | //Tone.Transport.stop(); 2011 | if (this.config && this.config.outputMidi) { 2012 | this.exportMidi(); 2013 | } 2014 | else { 2015 | Tone.ToneAudioBuffer.loaded().then(() => { 2016 | Tone.Transport.stop(); 2017 | Tone.Transport.start("+0.1"); 2018 | }); 2019 | } 2020 | } 2021 | catch(ex){ 2022 | throw ex; 2023 | } 2024 | } 2025 | 2026 | visitSectionDeclaration(node) { 2027 | } 2028 | 2029 | visitSyncProcedureCall(node){ 2030 | this.visitProcedureCall(node, true); 2031 | } 2032 | 2033 | visitProcedureCall(node, isSync) { 2034 | let procSymbol = node.procSymbol; 2035 | let ar = new HandelActivationRecord(node.value, ARTYPES.PROCEDURE, procSymbol.scopeLevel + 1); 2036 | ar.enclosingRecord = this.callStack.peek(); 2037 | 2038 | let prevCompositon = this.currentComposition; 2039 | if(!isSync){ 2040 | this.currentComposition = new Composition(Tone.FMSynth, 140, { trackName: node.value, midi: this.midi }); 2041 | this.currentComposition.enclosingComposition = prevCompositon; 2042 | } 2043 | 2044 | let formalParams = procSymbol.params; 2045 | let actualParams = node.actualParams; 2046 | for (let i = 0; i < formalParams.length; i++) { 2047 | let actualValue; 2048 | if (actualParams[i].token.type === FOR) { 2049 | actualValue = this.visitFor(actualParams[i]); 2050 | } 2051 | else if (actualParams[i].token.type === BEAT) { 2052 | actualValue = this.visitBeat(actualParams[i]); 2053 | } 2054 | else if (actualParams[i].token.type === ID) { 2055 | actualValue = this.visitId(actualParams[i]); 2056 | } 2057 | else if (actualParams[i].token.type === NOTE) { 2058 | actualValue = this.visitNoteList(actualParams[i]); 2059 | } 2060 | else if (actualParams[i].token.type === VERTICAL) { 2061 | actualValue = this.visitNoteGroup(actualParams[i]); 2062 | } 2063 | else if (actualParams[i].token.type === RANDINT) { 2064 | actualValue = this.visitRandint(actualParams[i]); 2065 | } 2066 | else if (actualParams[i].token.type === CHOOSE) { 2067 | actualValue = this.visitChoose(actualParams[i]); 2068 | } 2069 | else if (actualParams[i].token.type === SELECT) { 2070 | actualValue = this.visitSelect(actualParams[i]); 2071 | } 2072 | else { 2073 | actualValue = this.visitBinOp(actualParams[i]); 2074 | } 2075 | ar.setItem(formalParams[i].name, actualValue); 2076 | } 2077 | 2078 | if(node.customizationList){ 2079 | for (let customization of node.customizationList) { 2080 | if (customization.token.type === BPM) { 2081 | this.visitBPM(customization); 2082 | } 2083 | else if (customization.token.type === SOUND) { 2084 | this.visitSound(customization); 2085 | } 2086 | else if (customization.token.type === LOOP) { 2087 | this.visitLoop(customization); 2088 | } 2089 | else if (customization.token.type === VOLUME) { 2090 | this.visitVolume(customization); 2091 | } 2092 | else if (customization.token.type === PAN) { 2093 | this.visitPan(customization); 2094 | } 2095 | else if (customization.token.type === REVERB) { 2096 | this.visitReverb(customization); 2097 | } 2098 | } 2099 | } 2100 | 2101 | this.callStack.push(ar);; 2102 | 2103 | //execute body 2104 | this.visitStatementList(procSymbol.statementList); 2105 | 2106 | if(!isSync){ 2107 | this.currentComposition.play(); 2108 | } 2109 | 2110 | this.callStack.pop(ar); 2111 | if(!isSync){ 2112 | this.currentComposition = this.currentComposition.enclosingComposition; 2113 | } 2114 | } 2115 | 2116 | mapHelper(val, ogStart, ogEnd, newStart, newEnd) { 2117 | if(newStart > newEnd){ 2118 | let temp = newStart; 2119 | newStart = newEnd; 2120 | newEnd = temp; 2121 | } 2122 | let ratio = val / (Math.abs(ogStart) + Math.abs(ogEnd)); 2123 | let output = newStart + ((Math.abs(newEnd - newStart)) * ratio); 2124 | return output; 2125 | } 2126 | 2127 | chooseFromGroup(amt, notelists){ 2128 | let output = []; 2129 | let copy = [] ; 2130 | for(let i = 0; i < notelists.length; i++){ 2131 | copy.push(notelists[i].slice()); 2132 | } 2133 | while(amt > 0 && copy.length > 0){ 2134 | let i = Math.floor(Math.random() * copy.length); 2135 | for(let j = 0; j < copy[i].length; j++){ 2136 | output.push(copy[i][j]); 2137 | } 2138 | copy.splice(i, 1); 2139 | amt -= 1; 2140 | } 2141 | //console.log(output); 2142 | return output; 2143 | } 2144 | 2145 | visitSelect(node){ 2146 | let ind; 2147 | if(node.digitNode.token.type === RANDINT){ 2148 | ind = this.visitRandint(node.digitNode); 2149 | } 2150 | else { 2151 | ind = this.visitBinOp(node.digitNode); 2152 | } 2153 | let notesNode = node.notesNode; 2154 | let noteContainer; 2155 | let isGroup = false; 2156 | if(notesNode.token.type === ID){ 2157 | noteContainer = this.visitId(notesNode); 2158 | if(noteContainer.length > 0 && Array.isArray(noteContainer[0])){ 2159 | isGroup = true; 2160 | } 2161 | } 2162 | else if(notesNode.token.type === VERTICAL){ 2163 | noteContatiner = this.visitNoteGroup(notesNode); 2164 | isGroup = true; 2165 | } 2166 | else { 2167 | noteContainer = this.visitNoteList(notesNode); 2168 | } 2169 | 2170 | if(ind < 0){ 2171 | ind = 0; 2172 | } 2173 | if(isGroup){ 2174 | return noteContainer[Math.min(ind, noteContainer.length - 1)]; 2175 | } 2176 | else { 2177 | let arr = []; 2178 | arr.push(noteContainer[Math.min(ind, noteContainer.length - 1)]); 2179 | return arr; 2180 | } 2181 | } 2182 | 2183 | 2184 | visitChoose(node){ 2185 | let amt; 2186 | if(node.digitNode.token.type === RANDINT){ 2187 | amt = this.visitRandint(node.digitNode); 2188 | } 2189 | else { 2190 | amt = this.visitBinOp(node.digitNode); 2191 | } 2192 | let notesNode = node.notesNode; 2193 | let noteContainer; 2194 | if(notesNode.token.type === ID){ 2195 | noteContainer = this.visitId(notesNode); 2196 | if(noteContainer.length > 0 && Array.isArray(noteContainer[0])){ 2197 | return this.chooseFromGroup(amt, noteContainer); 2198 | } 2199 | } 2200 | else if(notesNode.token.type === VERTICAL){ 2201 | return this.chooseFromGroup(amt, this.visitNoteGroup(notesNode)); 2202 | } 2203 | else if(notesNode.token.type === NOTE){ 2204 | noteContainer = this.visitNoteList(notesNode); 2205 | } 2206 | else { 2207 | throw Error(`Type error in choose expression at line: ${node.token.lineno}`); 2208 | } 2209 | 2210 | if(!Array.isArray(noteContainer)){ 2211 | throw Error(`Type error in choose expression at line: ${node.token.lineno}`); 2212 | } 2213 | 2214 | let output = []; 2215 | let copy = noteContainer.slice(); 2216 | while(amt > 0 && copy.length > 0){ 2217 | let i = Math.floor(Math.random() * copy.length); 2218 | output.push(copy[i]); 2219 | copy.splice(i, 1); 2220 | amt -= 1; 2221 | } 2222 | return output; 2223 | } 2224 | 2225 | visitReverb(node) { 2226 | let child = node.value; 2227 | let value; 2228 | if(child.token.type === DIGIT){ 2229 | value = this.visitDigit(child); 2230 | } 2231 | else if (child.token.type === ID){ 2232 | value = this.visitId(child); 2233 | } 2234 | else if(child.token.type === RANDINT){ 2235 | value = this.visitRandint(child) 2236 | } 2237 | else if(child.token.type === MUL || child.token.type === DIV 2238 | || child.token.type === PLUS || child.token.type === MINUS 2239 | || child.token.type === MOD){ 2240 | value = this.visitBinOp(child) 2241 | } 2242 | if (value < 1) { 2243 | return 2244 | } 2245 | let reverb = value / 1000; 2246 | this.currentComposition.reverb = reverb; 2247 | } 2248 | 2249 | visitPan(node) { 2250 | let child = node.value; 2251 | let value; 2252 | if(child.token.type === DIGIT){ 2253 | value = this.visitDigit(child); 2254 | } 2255 | else if (child.token.type === ID){ 2256 | value = this.visitId(child); 2257 | } 2258 | else if(child.token.type === RANDINT){ 2259 | value = this.visitRandint(child) 2260 | } 2261 | else if(child.token.type === MUL || child.token.type === DIV 2262 | || child.token.type === PLUS || child.token.type === MINUS 2263 | || child.token.type === MOD){ 2264 | value = this.visitBinOp(child) 2265 | } 2266 | 2267 | if (value > 100 || value < 0) { 2268 | return 2269 | } 2270 | let pan = this.mapHelper(value, 0, 100, -1, 1); 2271 | this.currentComposition.pan = pan; 2272 | } 2273 | 2274 | visitVolume(node) { 2275 | let child = node.value; 2276 | let value; 2277 | if(child.token.type === DIGIT){ 2278 | value = this.visitDigit(child); 2279 | } 2280 | else if (child.token.type === ID){ 2281 | value = this.visitId(child); 2282 | } 2283 | else if(child.token.type === RANDINT){ 2284 | value = this.visitRandint(child) 2285 | } 2286 | else if(child.token.type === MUL || child.token.type === DIV 2287 | || child.token.type === PLUS || child.token.type === MINUS 2288 | || child.token.type === MOD){ 2289 | value = this.visitBinOp(child) 2290 | } 2291 | 2292 | if (value > 100 || value < 0) { 2293 | return 2294 | } 2295 | let vol = this.mapHelper(value, 0, 100, -70, 70); 2296 | this.currentComposition.volume = vol; 2297 | } 2298 | 2299 | visitBPM(node) { 2300 | let child = node.value; 2301 | let value; 2302 | if(child.token.type === DIGIT){ 2303 | value = this.visitDigit(child); 2304 | } 2305 | else if (child.token.type === ID){ 2306 | value = this.visitId(child); 2307 | } 2308 | else if(child.token.type === RANDINT){ 2309 | value = this.visitRandint(child) 2310 | } 2311 | else if(child.token.type === MUL || child.token.type === DIV 2312 | || child.token.type === PLUS || child.token.type === MINUS 2313 | || child.token.type === MOD){ 2314 | value = this.visitBinOp(child) 2315 | } 2316 | this.currentComposition.bpm = value; 2317 | return 2318 | } 2319 | 2320 | visitSound(node) { 2321 | let instrument = node.instrument; 2322 | if (instrument === 'kick') { 2323 | let kick = new Kick(this.increment).synth; 2324 | this.totalSamples += 1; 2325 | this.currentComposition.synth = kick; 2326 | } 2327 | else if (instrument === 'snare') { 2328 | let snare = new Snare(this.increment).synth; 2329 | this.totalSamples += 1; 2330 | this.currentComposition.synth = snare; 2331 | } 2332 | else if (instrument === 'hihat') { 2333 | let hihat = new HiHat(this.increment).synth; 2334 | this.totalSamples += 1; 2335 | this.currentComposition.synth = hihat; 2336 | } 2337 | else if (instrument === 'casio') { 2338 | let casio = new Casio(this.increment).synth; 2339 | this.totalSamples += 1; 2340 | this.currentComposition.synth = casio; 2341 | } 2342 | else if (instrument === 'synth') { 2343 | let synth = new FMSynth().synth; 2344 | this.currentComposition.synth = synth; 2345 | } 2346 | else if (instrument === 'piano') { 2347 | let piano = new Piano(this.increment).synth; 2348 | this.totalSamples += 1; 2349 | this.currentComposition.synth = piano; 2350 | } 2351 | else if (instrument === 'guitar') { 2352 | let piano = new Guitar(this.increment).synth; 2353 | this.totalSamples += 1; 2354 | this.currentComposition.synth = piano; 2355 | } 2356 | else { 2357 | let synth = this.callStack.peek().getItem(instrument); 2358 | //this.totalSamples += 1; 2359 | this.currentComposition.synth = synth; 2360 | } 2361 | } 2362 | 2363 | visitBoolean(node){ 2364 | return node.value; 2365 | } 2366 | 2367 | visitComparisonOperator(node){ 2368 | return node.value; 2369 | } 2370 | 2371 | visitConditionExpression(node){ 2372 | if(node.token.type === AND){ 2373 | return this.visitConditionFactor(node.left) && this.visitConditionFactor(node.right); 2374 | } 2375 | else if(node.token.type === OR){ 2376 | return this.visitConditionFactor(node.left) || this.visitConditionFactor(node.right); 2377 | } 2378 | else { 2379 | return this.visitConditionFactor(node); 2380 | } 2381 | } 2382 | 2383 | visitConditionFactor(node){ 2384 | if(node.token.type === AND || node.token.type === OR){ 2385 | return this.visitConditionExpression(node); 2386 | } 2387 | else { 2388 | return this.visitCondition(node); 2389 | } 2390 | } 2391 | 2392 | visitCondition(node){ 2393 | try{ 2394 | let leftValue; 2395 | let rightValue; 2396 | if(node.left.token.type === ID){ 2397 | leftValue = this.visitId(node.left); 2398 | } 2399 | else if (node.left.token.type === BEAT) { 2400 | leftValue = this.visitBeat(node.left); 2401 | } 2402 | else if (node.left.token.type === FOR) { 2403 | leftValue = this.visitFor(node.left); 2404 | } 2405 | else if (node.left.token.type === NOTE) { 2406 | leftValue = this.visitNoteList(node.left); 2407 | } 2408 | else if (node.left.token.type === DIGIT) { 2409 | leftValue = this.visitDigit(node.left); 2410 | } 2411 | else if (node.left.token.type === RANDINT) { 2412 | leftValue = this.visitRandint(node.left); 2413 | } 2414 | else if (node.left.token.type === CHOOSE) { 2415 | leftValue = this.visitChoose(node.left); 2416 | } 2417 | else if (node.left.token.type === SELECT) { 2418 | leftValue = this.visitSelect(node.left); 2419 | } 2420 | else { 2421 | leftValue = this.visitBinOp(node.left); 2422 | } 2423 | 2424 | if(node.right.token.type === ID){ 2425 | rightValue = this.visitId(node.right); 2426 | } 2427 | else if (node.right.token.type === BEAT) { 2428 | rightValue = this.visitBeat(node.right); 2429 | } 2430 | else if (node.right.token.type === FOR) { 2431 | rightValue = this.visitFor(node.right); 2432 | } 2433 | else if (node.right.token.type === NOTE) { 2434 | rightValue = this.visitNoteList(node.right); 2435 | } 2436 | else if (node.right.token.type === DIGIT) { 2437 | rightValue = this.visitDigit(node.right); 2438 | } 2439 | else if (node.right.token.type === RANDINT) { 2440 | rightValue = this.visitRandint(node.right); 2441 | } 2442 | else if (node.right.token.type === CHOOSE) { 2443 | rightValue = this.visitChoose(node.right); 2444 | } 2445 | else if (node.right.token.type === SELECT) { 2446 | rightValue = this.visitSelect(node.left); 2447 | } 2448 | else { 2449 | rightValue = this.visitBinOp(node.right); 2450 | } 2451 | 2452 | let operation = this.visitComparisonOperator(node.operator); 2453 | 2454 | if(typeof leftValue != typeof rightValue){ 2455 | throw Error(`Type error in condition statement at line: ${node.token.lineno}`); 2456 | } 2457 | 2458 | if(Array.isArray(leftValue)){ 2459 | return this.compareNoteLists(operation, leftValue, rightValue); 2460 | } 2461 | else if(typeof leftValue == "number"){ 2462 | return this.compareDigits(operation, leftValue, rightValue); 2463 | } 2464 | else if(typeof leftValue == "string"){ 2465 | return this.compareDigits(operation, parseInt(leftValue), parseInt(rightValue)); 2466 | } 2467 | else if(leftValue instanceof PlayEvent){ 2468 | return this.comparePlayevents(operation, leftValue, rightValue); 2469 | } 2470 | } 2471 | catch(ex){ 2472 | throw ex; 2473 | } 2474 | } 2475 | 2476 | compareNoteLists(op, notelist1, notelist2){ 2477 | if(op === "equalto" || op === "notequal"){ 2478 | if(notelist1.length !== notelist2.length){ 2479 | return false; 2480 | } 2481 | let eq = true; 2482 | for(let i = 0; i < notelist1.length; i++){ 2483 | if(!notelist2.includes(notelist1[i])){ 2484 | eq = false; 2485 | }; 2486 | } 2487 | 2488 | if(op === "notequal"){ 2489 | return !eq; 2490 | } 2491 | return eq; 2492 | } 2493 | else { 2494 | let leftsum = 0; 2495 | let rightsum = 0; 2496 | for(let i = 0; i < notelist1.length; i++){ 2497 | leftsum += Tone.Frequency(notelist1[i]).toFrequency(); 2498 | } 2499 | for(let i = 0; i < notelist2.length; i++){ 2500 | rightsum += Tone.Frequency(notelist2[i]).toFrequency(); 2501 | } 2502 | if(op === "greaterthan"){ 2503 | return leftsum > rightsum 2504 | } 2505 | if(op === "greatereq"){ 2506 | return leftsum >= rightsum 2507 | } 2508 | if(op === "lesseq"){ 2509 | return leftsum <= rightsum 2510 | } 2511 | else if(op === "lessthan"){ 2512 | return leftsum < rightsum; 2513 | } 2514 | } 2515 | } 2516 | comparePlayevents(op, event1, event2){ 2517 | if(event1.notes && event2.notes){ 2518 | return this.compareNoteLists(op, event1.notes, event2.notes); 2519 | } 2520 | else { 2521 | this.compareDigits(op, event1.numBeats, event2.numBeats); 2522 | } 2523 | } 2524 | compareDigits(op, val1, val2){ 2525 | if(op === "equalto"){ 2526 | return val1 === val2; 2527 | } 2528 | else if(op === "notequal"){ 2529 | return val1 !== val2; 2530 | } 2531 | else if(op === "greaterthan"){ 2532 | return val1 > val2; 2533 | } 2534 | else if(op === "greatereq"){ 2535 | return val1 >= val2; 2536 | } 2537 | else if(op === "lesseq"){ 2538 | return val1 <= val2; 2539 | } 2540 | else if(op === "lessthan"){ 2541 | return val1 < val2; 2542 | } 2543 | } 2544 | 2545 | visitConditionalStatement(node){ 2546 | try { 2547 | let decision = this.visitConditionExpression(node.condition); 2548 | if(decision){ 2549 | this.visitStatementList(node.ifStatementList); 2550 | } 2551 | else if(node.elseStatementList){ 2552 | this.visitStatementList(node.elseStatementList); 2553 | } 2554 | } 2555 | catch(ex){ 2556 | throw ex; 2557 | } 2558 | } 2559 | 2560 | visitLoop(node) { 2561 | let child = node.value; 2562 | let value; 2563 | if(child.token.type === DIGIT){ 2564 | value = this.visitDigit(child); 2565 | } 2566 | else if (child.token.type === ID){ 2567 | value = this.visitId(child); 2568 | } 2569 | else if(child.token.type === RANDINT){ 2570 | value = this.visitRandint(child) 2571 | } 2572 | else if(child.token.type === MUL || child.token.type === DIV 2573 | || child.token.type === PLUS || child.token.type === MINUS 2574 | || child.token.type === MOD){ 2575 | value = this.visitBinOp(child) 2576 | } 2577 | if(value < 1){ 2578 | return; 2579 | } 2580 | 2581 | this.currentComposition.loopTimes = value; 2582 | return; 2583 | } 2584 | 2585 | visitStatementList(node) { 2586 | try{ 2587 | for (let child of node.children) { 2588 | if (child.token.type === PLAY) { 2589 | this.visitPlay(child); 2590 | } 2591 | else if (child.token.type === REST) { 2592 | this.visitRest(child); 2593 | } 2594 | else if (child.token.type === ID) { 2595 | if (child.token.category === "SYNC") { 2596 | this.visitSyncProcedureCall(child); 2597 | } 2598 | else if(child.token.category){ 2599 | this.visitProcedureCall(child); 2600 | } 2601 | else { 2602 | this.visitSectionDeclaration(child); 2603 | } 2604 | } 2605 | else if (child.token.type === ASSIGN) { 2606 | this.visitSave(child); 2607 | } 2608 | else if (child.token.type === UPDATE) { 2609 | this.visitUpdate(child); 2610 | } 2611 | else if (child.token.type === BLOCK) { 2612 | this.visitBlockLoop(child); 2613 | } 2614 | else if (child.token.type === IF) { 2615 | this.visitConditionalStatement(child); 2616 | } 2617 | else if (child.token.type === LOAD) { 2618 | this.visitLoad(child); 2619 | } 2620 | } 2621 | } 2622 | catch(ex){throw ex;} 2623 | } 2624 | 2625 | visitLoad(node) { 2626 | try { 2627 | if (node.instrumentName in this.config.instruments) { 2628 | this.callStack.peek().setItem(node.localVarName, 2629 | this.config.instruments[node.instrumentName]); 2630 | } 2631 | else { 2632 | throw Error(`invalid instrument at line: ${node.token.lineno}`); 2633 | } 2634 | } 2635 | catch (ex) { 2636 | throw ex; 2637 | } 2638 | } 2639 | 2640 | visitBlockLoop(node) { 2641 | try { 2642 | let loopNode = node.loopTimes; 2643 | let whileCondition = node.whileCondition; 2644 | let value; 2645 | if(loopNode){ 2646 | if(loopNode.token.type === DIGIT){ 2647 | value = this.visitDigit(loopNode); 2648 | } 2649 | else if(loopNode.token.type === ID){ 2650 | value = this.visitId(loopNode); 2651 | } 2652 | else { 2653 | value = this.visitBinOp(loopNode); 2654 | } 2655 | for (let i = 0; i < value; i++) { 2656 | this.visitStatementList(node.statementList); 2657 | } 2658 | } 2659 | else if(whileCondition){ 2660 | while(this.visitConditionExpression(whileCondition)){ 2661 | this.visitStatementList(node.statementList); 2662 | } 2663 | } 2664 | } 2665 | catch(ex){ 2666 | throw ex; 2667 | } 2668 | } 2669 | 2670 | visitPlay(node) { 2671 | if (node.child.token.type === FOR) { 2672 | let forNode = node.child; 2673 | this.currentComposition.configurePart([this.visitFor(forNode, node.rep)]); 2674 | } 2675 | else if (node.child.token.type === ID) { 2676 | this.currentComposition.configurePart([this.visitId(node.child, node.rep)]); 2677 | } 2678 | } 2679 | 2680 | visitRest(node) { 2681 | let child = node.child; 2682 | let events; 2683 | if (child.token.type === FOR) { 2684 | this.visitFor(child); 2685 | events = [this.visitFor(child)]; 2686 | } 2687 | else if (child.token.type === ID) { 2688 | events = [new PlayEvent(null, "", this.visitId(child))]; 2689 | } 2690 | else { 2691 | this.error(); 2692 | } 2693 | 2694 | this.currentComposition.configurePart(events); 2695 | } 2696 | 2697 | error() { 2698 | } 2699 | 2700 | 2701 | visitBinOp(node){ 2702 | try { 2703 | let op = node.token; 2704 | if(op.type === DIGIT){ 2705 | return this.visitDigit(node); 2706 | } 2707 | else if(op.type === ID){ 2708 | let value = this.visitId(node); 2709 | if(typeof value !== "number"){ 2710 | throw Error(`Type error in eval expression in line: ${node.token.lineno}`); 2711 | } 2712 | return value; 2713 | } 2714 | else if(op.type === MUL){ 2715 | return this.visitBinOp(node.left) * this.visitBinOp(node.right); 2716 | } 2717 | else if(op.type === DIV){ 2718 | return Math.floor(this.visitBinOp(node.left) / this.visitBinOp(node.right)); 2719 | } 2720 | else if(op.type === MOD){ 2721 | return this.visitBinOp(node.left) % this.visitBinOp(node.right); 2722 | } 2723 | else if(op.type === PLUS){ 2724 | return this.visitBinOp(node.left) + this.visitBinOp(node.right); 2725 | } 2726 | else if(op.type === MINUS){ 2727 | return this.visitBinOp(node.left) - this.visitBinOp(node.right); 2728 | } 2729 | } 2730 | catch(ex){ 2731 | throw ex; 2732 | } 2733 | } 2734 | 2735 | 2736 | visitNoteGroup(node){ 2737 | let arr = []; 2738 | for(let listNode of node.notelists){ 2739 | if(listNode.token.type === ID){ 2740 | let curr = this.visitId(listNode); 2741 | if(!Array.isArray(curr)){ 2742 | throw Error(`Type error non notelist in notegroup: ${listNode.token.lineno}`); 2743 | } 2744 | arr.push(curr); 2745 | } 2746 | else { 2747 | arr.push(this.visitNoteList(listNode)); 2748 | } 2749 | } 2750 | return arr; 2751 | } 2752 | 2753 | visitSave(node) { 2754 | try { 2755 | let varNode = node.left; 2756 | let valueNode = node.right; 2757 | let value; 2758 | if (valueNode.token.type === ID) { 2759 | value = this.visitId(valueNode); 2760 | } 2761 | else if (valueNode.token.type === BEAT) { 2762 | value = this.visitBeat(valueNode); 2763 | } 2764 | else if (valueNode.token.type === FOR) { 2765 | value = this.visitFor(valueNode); 2766 | } 2767 | else if (valueNode.token.type === NOTE) { 2768 | value = this.visitNoteList(valueNode); 2769 | } 2770 | else if (valueNode.token.type === DIGIT) { 2771 | value = this.visitDigit(valueNode); 2772 | } 2773 | else if (valueNode.token.type === RANDINT) { 2774 | value = this.visitRandint(valueNode); 2775 | } 2776 | else if (valueNode.token.type === CHOOSE) { 2777 | value = this.visitChoose(valueNode); 2778 | } 2779 | else if (valueNode.token.type === SELECT) { 2780 | value = this.visitSelect(valueNode); 2781 | } 2782 | else if (valueNode.token.type === VERTICAL) { 2783 | value = this.visitNoteGroup(valueNode); 2784 | } 2785 | else { 2786 | value = this.visitBinOp(valueNode); 2787 | } 2788 | this.callStack.peek().setItem(varNode.value, value); 2789 | } 2790 | catch(ex){ 2791 | throw ex; 2792 | } 2793 | } 2794 | 2795 | visitDigit(node){ 2796 | return node.value; 2797 | } 2798 | 2799 | visitRandint(node){ 2800 | try { 2801 | let start = node.start; 2802 | let end = node.end; 2803 | let startval; 2804 | let endval; 2805 | if(start.token.type === ID){ 2806 | startval = this.visitId(start); 2807 | } 2808 | else if(start.token.type === RANDINT){ 2809 | startval = this.visitRandint(start); 2810 | } 2811 | else if((start.token.type === MUL 2812 | || start.token.type === DIV || start.token.type === MOD 2813 | || start.token.type === PLUS || start.token.type === MINUS 2814 | || start.token.type === DIGIT 2815 | )){ 2816 | startval = this.visitBinOp(start); 2817 | } 2818 | 2819 | if(end.token.type === ID){ 2820 | endval = this.visitId(end); 2821 | } 2822 | else if(end.token.type === RANDINT){ 2823 | endval = this.visitRandint(end); 2824 | } 2825 | else if((end.token.type === MUL 2826 | || end.token.type === DIV || end.token.type === MOD 2827 | || end.token.type === PLUS || end.token.type === MINUS 2828 | || end.token.type === DIGIT 2829 | )){ 2830 | endval = this.visitBinOp(end); 2831 | } 2832 | 2833 | if(typeof startval !== "number" || typeof endval !== "number"){ 2834 | throw Error(`Type error in randint expression in line: ${node.token.lineno}`); 2835 | } 2836 | let num = Math.floor(Math.random() * Math.floor(100)); 2837 | let output = Math.floor(this.mapHelper(num, 0, 100, startval, endval)); 2838 | return output; 2839 | } 2840 | catch(ex){ 2841 | throw ex; 2842 | } 2843 | } 2844 | 2845 | transposeNotelist(varNode, shiftAmt, shiftTarget){ 2846 | for (let i = 0; i < shiftTarget.length; i++) { 2847 | shiftTarget[i] = Tone.Frequency(shiftTarget[i]).transpose(shiftAmt); 2848 | } 2849 | return shiftTarget.slice(); 2850 | } 2851 | visitUpdate(node) { 2852 | if (!node.isShift) { 2853 | this.visitSave(node); 2854 | } 2855 | else { 2856 | let varNode = node.left; 2857 | let valueNode = node.right; 2858 | let shiftSymbol = valueNode.symbol; 2859 | let shiftAmt = this.visitShift(valueNode); 2860 | let shiftTarget = this.callStack.peek().getItem(varNode.value); 2861 | if(Array.isArray(shiftTarget)){ 2862 | let output = []; 2863 | if(shiftTarget.length > 0 && Array.isArray(shiftTarget[0])){ 2864 | for(let i = 0; i < shiftTarget.length; i++){ 2865 | output.push(this.transposeNotelist(varNode, shiftAmt, shiftTarget[i])); 2866 | } 2867 | this.callStack.peek().setItem(varNode.value, output); 2868 | } 2869 | else{ 2870 | output = this.transposeNotelist(varNode, shiftAmt, shiftTarget); 2871 | this.callStack.peek().setItem(varNode.value, output); 2872 | } 2873 | } 2874 | else if(typeof shiftTarget == "number"){ 2875 | this.callStack.peek().setItem(varNode.value, shiftTarget + shiftAmt); 2876 | } 2877 | else if(typeof shiftTarget == "string"){ 2878 | this.callStack.peek().setItem(varNode.value, JSON.stringify(parseInt(shiftTarget) + shiftAmt)); 2879 | } 2880 | else if(shiftTarget instanceof PlayEvent){ 2881 | if(shiftTarget.notes){ 2882 | let notes = shiftTarget.notes.slice(); 2883 | for (let i = 0; i < notes.length; i++) { 2884 | notes[i] = Tone.Frequency(notes[i]).transpose(shiftAmt); 2885 | } 2886 | shiftTarget.notes = notes; 2887 | } 2888 | else { 2889 | shiftTarget.numBeats += shiftAmt; 2890 | } 2891 | } 2892 | } 2893 | } 2894 | visitShift(node) { 2895 | let shiftAmount; 2896 | let shiftNode = node.shiftNode; 2897 | if(shiftNode.token.type === ID){ 2898 | shiftAmount = this.visitId(node.shiftNode); 2899 | shiftAmount = parseInt(shiftAmount); 2900 | } 2901 | else if(shiftNode.token.type === RANDINT){ 2902 | shiftAmount = this.visitRandint(shiftNode); 2903 | } 2904 | else{ 2905 | shiftAmount = this.visitBinOp(shiftNode); 2906 | } 2907 | 2908 | if (node.token.value === 'lshift') { 2909 | return -1 * shiftAmount; 2910 | } 2911 | return shiftAmount; 2912 | } 2913 | 2914 | visitId(node) { 2915 | return this.callStack.peek().get(node.value); 2916 | } 2917 | 2918 | visitFor(node, rep) { 2919 | let value = rep ? rep.value : 1 2920 | 2921 | if (node.right) { 2922 | let notelist; 2923 | let duration = node.right.value; 2924 | if (node.left.token.type === ID) { 2925 | notelist = this.visitId(node.left); 2926 | } 2927 | else if(node.left.token.type === CHOOSE){ 2928 | notelist = this.visitChoose(node.left); 2929 | } 2930 | else if(node.left.token.type === SELECT){ 2931 | notelist = this.visitSelect(node.left); 2932 | } 2933 | else{ 2934 | notelist = this.visitNoteList(node.left); 2935 | } 2936 | if (node.right.token.type === ID) { 2937 | duration = this.visitId(node.right); 2938 | } 2939 | let nl = notelist.slice(); 2940 | return new PlayEvent(nl, this.beatToValue[duration], duration, value); 2941 | } 2942 | else { 2943 | return new PlayEvent(null, this.beatToValue[node.left.value], node.left.value, value); 2944 | } 2945 | } 2946 | 2947 | visitNoteList(node) { 2948 | let notes = []; 2949 | while (node != null) { 2950 | notes.push(node.value) 2951 | node = node.child; 2952 | } 2953 | return notes; 2954 | } 2955 | 2956 | visitNote(node) { 2957 | return node.value; 2958 | } 2959 | 2960 | visitBeat(node) { 2961 | return node.value; 2962 | } 2963 | } 2964 | 2965 | 2966 | class SymbolTableBuilder { 2967 | constructor() { 2968 | this.currentScope; 2969 | } 2970 | 2971 | visitProgram(node) { 2972 | try { 2973 | this.currentScope = new HandelSymbolTable('global', 1, null); 2974 | 2975 | //subtree 2976 | this.visitStatementList(node.child); 2977 | 2978 | this.currentScope = this.currentScope.enclosingScope; 2979 | } 2980 | catch (ex) { 2981 | throw ex; 2982 | } 2983 | } 2984 | 2985 | visitBoolean(node){ 2986 | } 2987 | 2988 | visitComparisonOperator(node){ 2989 | } 2990 | 2991 | visitConditionExpression(node){ 2992 | if(node.token.type === AND){ 2993 | this.visitConditionFactor(node.left); 2994 | this.visitConditionFactor(node.right); 2995 | } 2996 | else if(node.token.type === OR) { 2997 | this.visitConditionFactor(node.left); 2998 | this.visitConditionFactor(node.right); 2999 | } 3000 | else { 3001 | this.visitConditionFactor(node); 3002 | } 3003 | } 3004 | 3005 | visitConditionFactor(node){ 3006 | if(node.token.type === AND || node.token.type === OR){ 3007 | this.visitConditionExpression(node); 3008 | } 3009 | else { 3010 | this.visitCondition(node); 3011 | } 3012 | } 3013 | 3014 | visitCondition(node){ 3015 | try{ 3016 | if(node.left && node.left.token.type === ID){ 3017 | this.visitId(node.left); 3018 | } 3019 | else if(node.left && node.left.token.type === RANDINT){ 3020 | this.visitRandint(node.left); 3021 | } 3022 | else if(node.left && node.left.token.type === CHOOSE){ 3023 | this.visitChoose(node.left); 3024 | } 3025 | else if(node.left && node.left.token.type === SELECT){ 3026 | this.visitSelect(node.left); 3027 | } 3028 | else if(node.left && (node.left.token.type === MUL 3029 | || node.left.token.type === DIV || node.left.token.type === MOD 3030 | || node.left.token.type === PLUS || node.left.token.type === MINUS 3031 | || node.left.token.type === DIGIT 3032 | )){ 3033 | this.visitBinOp(node.left); 3034 | } 3035 | 3036 | if(node.right && node.right.token.type === ID){ 3037 | this.visitId(node.right); 3038 | } 3039 | else if(node.right && node.right.token.type === RANDINT){ 3040 | this.visitRandint(node.right); 3041 | } 3042 | else if(node.right && node.right.token.type === CHOOSE){ 3043 | this.visitChoose(node.right); 3044 | } 3045 | else if(node.right && node.right.token.type === SELECT){ 3046 | this.visitSelect(node.right); 3047 | } 3048 | else if(node.right && (node.right.token.type === MUL 3049 | || node.right.token.type === DIV || node.right.token.type === MOD 3050 | || node.right.token.type === PLUS || node.right.token.type === MINUS 3051 | || node.right.token.type === DIGIT 3052 | )){ 3053 | this.visitBinOp(node.right); 3054 | } 3055 | } 3056 | catch(ex){ 3057 | throw ex; 3058 | } 3059 | } 3060 | 3061 | visitConditionalStatement(node){ 3062 | try { 3063 | this.visitConditionExpression(node.condition); 3064 | this.visitStatementList(node.ifStatementList); 3065 | if(node.elseStatementList){ 3066 | this.visitStatementList(node.elseStatementList); 3067 | } 3068 | } 3069 | catch(ex){ 3070 | throw ex; 3071 | } 3072 | } 3073 | 3074 | visitNoteGroup(node){ 3075 | try { 3076 | for(let listNode of node.notelists) { 3077 | if(listNode.token.type === ID){ 3078 | this.visitId(listNode); 3079 | } 3080 | } 3081 | } 3082 | catch(ex){ 3083 | throw ex; 3084 | } 3085 | } 3086 | 3087 | visitSelect(node){ 3088 | try{ 3089 | this.visitChoose(node); 3090 | } 3091 | catch(ex){ 3092 | throw ex; 3093 | } 3094 | } 3095 | 3096 | 3097 | visitChoose(node){ 3098 | try{ 3099 | let ind; 3100 | if(node.digitNode.token.type === RANDINT){ 3101 | this.visitRandint(node.digitNode); 3102 | } 3103 | else { 3104 | this.visitBinOp(node.digitNode); 3105 | } 3106 | let notesNode = node.notesNode; 3107 | if(notesNode.token.type === ID){ 3108 | this.visitId(notesNode); 3109 | let varSymbol = this.currentScope.lookup(notesNode.token.value); 3110 | if(varSymbol.type.name !== "ANY" && varSymbol.type.name !== "NOTELIST" && varSymbol.type.name !== "NOTEGROUP"){ 3111 | throw Error(`Type error in the choose expression at line: ${notesNode.token.lineno}`); 3112 | } 3113 | } 3114 | } 3115 | catch(ex){ 3116 | throw ex; 3117 | } 3118 | } 3119 | 3120 | visitSectionDeclaration(node) { 3121 | try { 3122 | let procName = node.value; 3123 | let procSymbol = new ProcedureSymbol(procName); 3124 | this.currentScope.define(procSymbol); 3125 | 3126 | this.currentScope = new HandelSymbolTable(procName, 3127 | this.currentScope.scopeLevel + 1, this.currentScope); 3128 | 3129 | if (node.parameterList) { 3130 | for (let param of node.parameterList.children) { 3131 | let paramType = this.currentScope.lookup('ANY'); 3132 | let paramName = param.value; 3133 | let varSymbol = new VarSymbol(paramName, paramType); 3134 | this.currentScope.define(varSymbol); 3135 | procSymbol.params.push(varSymbol); 3136 | } 3137 | } 3138 | 3139 | //save pointer to statement list contained in the procedure 3140 | procSymbol.statementList = node.statementList; 3141 | 3142 | this.visitStatementList(node.statementList); 3143 | 3144 | this.currentScope = this.currentScope.enclosingScope; 3145 | } 3146 | catch (ex) { 3147 | throw ex; 3148 | } 3149 | } 3150 | 3151 | genericDigitVisit(node){ 3152 | let child = node.value; 3153 | let value; 3154 | if(child.token.type === DIGIT){ 3155 | value = this.visitDigit(child); 3156 | } 3157 | else if (child.token.type === ID){ 3158 | value = this.visitId(child); 3159 | } 3160 | else if(child.token.type === RANDINT){ 3161 | value = this.visitRandint(child) 3162 | } 3163 | else if(child.token.type === MUL || child.token.type === DIV 3164 | || child.token.type === PLUS || child.token.type === MINUS 3165 | || child.token.type === MOD){ 3166 | value = this.visitBinOp(child) 3167 | } 3168 | } 3169 | 3170 | visitBPM(node) { 3171 | this.genericDigitVisit(node); 3172 | } 3173 | 3174 | visitLoop(node) { 3175 | this.genericDigitVisit(node); 3176 | } 3177 | 3178 | visitSound(node) { 3179 | } 3180 | 3181 | visitVolume(node) { 3182 | this.genericDigitVisit(node); 3183 | } 3184 | 3185 | visitReverb(node) { 3186 | this.genericDigitVisit(node); 3187 | } 3188 | 3189 | visitPan(node) { 3190 | this.genericDigitVisit(node); 3191 | } 3192 | 3193 | visitDigit(node){ 3194 | } 3195 | 3196 | visitProcedureCall(node) { 3197 | try { 3198 | let procSymbol = this.currentScope.lookup(node.value) 3199 | if (!procSymbol) { 3200 | throw Error(`invalid chunk name at line: ${node.token.lineno}`); 3201 | } 3202 | let formalParams = procSymbol.params 3203 | if (node.actualParams.length != formalParams.length) { 3204 | throw Error(`invalid arguments at line: ${node.token.lineno}`); 3205 | } 3206 | 3207 | node.procSymbol = procSymbol; 3208 | let actualParams = node.actualParams; 3209 | for (let i = 0; i < formalParams.length; i++) { 3210 | if (actualParams[i].token.type === FOR) { 3211 | this.visitFor(actualParams[i]); 3212 | } 3213 | else if (actualParams[i].token.type === BEAT) { 3214 | this.visitBeat(actualParams[i]); 3215 | } 3216 | else if (actualParams[i].token.type === ID) { 3217 | this.visitId(actualParams[i]); 3218 | } 3219 | else if (actualParams[i].token.type === NOTE) { 3220 | this.visitNoteList(actualParams[i]); 3221 | } 3222 | else if (actualParams[i].token.type === VERTICAL) { 3223 | this.visitNoteGroup(actualParams[i]); 3224 | } 3225 | else if (actualParams[i].token.type === RANDINT) { 3226 | this.visitRandint(actualParams[i]); 3227 | } 3228 | else if (actualParams[i].token.type === CHOOSE) { 3229 | this.visitChoose(actualParams[i]); 3230 | } 3231 | else if (actualParams[i].token.type === SELECT) { 3232 | this.visitSelect(actualParams[i]); 3233 | } 3234 | else { 3235 | this.visitBinOp(actualParams[i]); 3236 | } 3237 | } 3238 | 3239 | if(node.customizationList){ 3240 | for (let customization of node.customizationList) { 3241 | if (customization.token.type === BPM) { 3242 | this.visitBPM(customization); 3243 | } 3244 | else if (customization.token.type === SOUND) { 3245 | this.visitSound(customization); 3246 | } 3247 | else if (customization.token.type === LOOP) { 3248 | this.visitLoop(customization); 3249 | } 3250 | else if (customization.token.type === VOLUME) { 3251 | this.visitVolume(customization); 3252 | } 3253 | else if (customization.token.type === PAN) { 3254 | this.visitPan(customization); 3255 | } 3256 | else if (customization.token.type === REVERB) { 3257 | this.visitReverb(customization); 3258 | } 3259 | } 3260 | } 3261 | } 3262 | catch (ex) { 3263 | throw ex; 3264 | } 3265 | } 3266 | 3267 | visitStatementList(node) { 3268 | try { 3269 | for (let child of node.children) { 3270 | if (child.token.type === PLAY) { 3271 | this.visitPlay(child); 3272 | } 3273 | else if (child.token.type === REST) { 3274 | this.visitRest(child); 3275 | } 3276 | else if (child.token.type === ID) { 3277 | if (child.token.category) { 3278 | this.visitProcedureCall(child); 3279 | } 3280 | else { 3281 | this.visitSectionDeclaration(child); 3282 | } 3283 | } 3284 | else if (child.token.type === ASSIGN) { 3285 | this.visitSave(child); 3286 | } 3287 | else if (child.token.type === UPDATE) { 3288 | this.visitUpdate(child); 3289 | } 3290 | else if (child.token.type === BLOCK) { 3291 | this.visitBlockLoop(child); 3292 | } 3293 | else if (child.token.type === LOAD) { 3294 | this.visitLoad(child); 3295 | } 3296 | else if (child.token.type === IF) { 3297 | this.visitConditionalStatement(child); 3298 | } 3299 | } 3300 | } 3301 | catch (ex) { 3302 | throw ex; 3303 | } 3304 | } 3305 | 3306 | visitLoad(node) { 3307 | let anyType = this.currentScope.lookup('ANY'); 3308 | let localVarName = node.localVarName; 3309 | let varSymbol = new VarSymbol(localVarName, anyType); 3310 | this.currentScope.define(varSymbol); 3311 | } 3312 | 3313 | visitBlockLoop(node) { 3314 | this.visitStatementList(node.statementList); 3315 | if(node.whileCondition){ 3316 | this.visitConditionExpression(node.whileCondition); 3317 | return; 3318 | } 3319 | let loopNode = node.loopTimes; 3320 | if(loopNode.token.type === ID){ 3321 | this.visitId(loopNode); 3322 | } 3323 | else if(this.isBinOp(loopNode.token)){ 3324 | this.visitBinOp(loopNode); 3325 | } 3326 | 3327 | } 3328 | 3329 | isBinOp(token){ 3330 | if(token.type === MUL || token.type === DIV || token.type === PLUS || token.type === MINUS 3331 | || token.type === MOD){ 3332 | return true; 3333 | } 3334 | return false; 3335 | } 3336 | 3337 | visitPlay(node) { 3338 | try { 3339 | if (node.child.token.type === FOR) { 3340 | let forNode = node.child; 3341 | this.visitFor(forNode) 3342 | } 3343 | else if (node.child.token.type === ID) { 3344 | this.visitId(node.child) 3345 | } 3346 | 3347 | if (node.rep) { 3348 | this.visitRep(node.rep); 3349 | } 3350 | } 3351 | catch (ex) { 3352 | throw ex; 3353 | } 3354 | } 3355 | 3356 | visitRep(node) { 3357 | } 3358 | 3359 | visitRest(node) { 3360 | try { 3361 | let child = node.child; 3362 | if (child.token.type === FOR) { 3363 | this.visitFor(child); 3364 | } 3365 | else if (child.token.type === ID) { 3366 | this.visitId(child); 3367 | } 3368 | else { 3369 | this.error(); 3370 | } 3371 | } 3372 | catch (ex) { 3373 | throw ex; 3374 | } 3375 | } 3376 | 3377 | visitSave(node) { 3378 | try { 3379 | let varNode = node.left; 3380 | let varName = varNode.token.value; 3381 | let valueNode = node.right; 3382 | if (this.currentScope.lookup(varName, true)) { 3383 | throw Error(`Name error in line ${varNode.token.lineno}: ${varName} already exists`); 3384 | } 3385 | let varSymbol; 3386 | if (valueNode.token.type === ID) { 3387 | this.visitId(valueNode); 3388 | let symbol = this.currentScope.lookup(valueNode.value, true); 3389 | if (!symbol) { 3390 | throw Error(`Name error in line ${valueNode.token.lineno}: ${valueNode.value} does not exist`); 3391 | } 3392 | let type = symbol.type; 3393 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup(type.name)); 3394 | } 3395 | else if (valueNode.token.type === BEAT) { 3396 | this.visitBeat(valueNode); 3397 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup('BEAT')); 3398 | } 3399 | else if (valueNode.token.type === FOR) { 3400 | this.visitFor(valueNode); 3401 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup('PLAYABLE')); 3402 | } 3403 | else if (valueNode.token.type === NOTE) { 3404 | this.visitNoteList(valueNode); 3405 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup('NOTELIST')); 3406 | } 3407 | else if (valueNode.token.type === DIGIT) { 3408 | this.visitDigit(valueNode); 3409 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup('DIGIT')); 3410 | } 3411 | else if (valueNode.token.type === RANDINT) { 3412 | this.visitRandint(valueNode); 3413 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup('DIGIT')); 3414 | } 3415 | else if (valueNode.token.type === CHOOSE) { 3416 | this.visitChoose(valueNode); 3417 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup('NOTELIST')); 3418 | } 3419 | else if (valueNode.token.type === SELECT) { 3420 | this.visitSelect(valueNode); 3421 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup('NOTELIST')); 3422 | } 3423 | else if (valueNode.token.type === VERTICAL) { 3424 | this.visitNoteGroup(valueNode); 3425 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup('NOTEGROUP')); 3426 | } 3427 | else{ 3428 | this.visitBinOp(valueNode); 3429 | varSymbol = new VarSymbol(varNode.token.value, this.currentScope.lookup('DIGIT')); 3430 | } 3431 | this.currentScope.define(varSymbol); 3432 | } 3433 | catch (ex) { 3434 | throw ex; 3435 | } 3436 | } 3437 | visitBinOp(node){ 3438 | let op = node.token; 3439 | if(op.type === DIGIT){ 3440 | this.visitDigit(node); 3441 | } 3442 | else if(op.type === ID){ 3443 | this.visitId(node); 3444 | } 3445 | if(node.left){ 3446 | this.visitBinOp(node.left); 3447 | } 3448 | if(node.right){ 3449 | this.visitBinOp(node.right); 3450 | } 3451 | } 3452 | 3453 | visitRandint(node){ 3454 | try { 3455 | let start = node.start; 3456 | let end = node.end; 3457 | if(start.token.type === ID){ 3458 | this.visitId(start); 3459 | } 3460 | else if(start.token.type === RANDINT){ 3461 | this.visitRandint(start); 3462 | } 3463 | else if((start.token.type === MUL 3464 | || start.token.type === DIV || start.token.type === MOD 3465 | || start.token.type === PLUS || start.token.type === MINUS 3466 | )){ 3467 | this.visitBinOp(start); 3468 | } 3469 | 3470 | if(end.token.type === ID){ 3471 | this.visitId(end); 3472 | } 3473 | else if(end.token.type === RANDINT){ 3474 | this.visitRandint(end); 3475 | } 3476 | else if((end.token.type === MUL 3477 | || end.token.type === DIV || end.token.type === MOD 3478 | || end.token.type === PLUS || end.token.type === MINUS 3479 | )){ 3480 | this.visitBinOp(end); 3481 | } 3482 | } 3483 | catch(ex){ 3484 | throw ex; 3485 | } 3486 | } 3487 | 3488 | visitShift(node) { 3489 | try { 3490 | let shiftNode = node.shiftNode; 3491 | let shiftToken = shiftNode.token; 3492 | if(shiftNode.token.type === ID){ 3493 | let found = this.currentScope.lookup(shiftToken.value); 3494 | if(!found){ 3495 | throw Error(`Name error in line ${shiftToken.lineno}: ${shiftToken.value} does not exist`); 3496 | } 3497 | if(found.type.name !== "DIGIT"){ 3498 | throw Error(`Type error in line ${shiftToken.lineno}: ${shiftToken.value} is not of type DIGIT`); 3499 | } 3500 | } 3501 | else if(shiftNode.token.type === RANDINT){ 3502 | this.visitRandint(node.shiftNode); 3503 | } 3504 | else if((shiftNode.token.type === MUL 3505 | || shiftNode.token.type === DIV || shiftNode.token.type === MOD 3506 | || shiftNode.token.type === PLUS || shiftNode.token.type === MINUS 3507 | )){ 3508 | this.visitBinOp(shiftNode); 3509 | } 3510 | } 3511 | catch(ex){ 3512 | throw ex; 3513 | } 3514 | } 3515 | 3516 | visitUpdate(node) { 3517 | try { 3518 | let varNode = node.left; 3519 | let varName = varNode.token.value; 3520 | let valueNode = node.right; 3521 | 3522 | this.visitId(varNode); 3523 | 3524 | if (node.isShift) { 3525 | this.visitShift(valueNode); 3526 | return; 3527 | } 3528 | 3529 | if (!this.currentScope.lookup(varName)) { 3530 | throw Error(`Name error in line ${varNode.token.lineno}: ${varName} does not exist`); 3531 | } 3532 | if (valueNode.token.type === ID) { 3533 | this.visitId(valueNode); 3534 | let symbol = this.currentScope.lookup(valueNode.value); 3535 | if (!symbol) { 3536 | throw Error(`Name error in line ${valueNode.token.lineno}: ${valueNode.value} does not exist`); 3537 | } 3538 | } 3539 | else if (valueNode.token.type === BEAT) { 3540 | this.visitBeat(valueNode); 3541 | } 3542 | else if (valueNode.token.type === FOR) { 3543 | this.visitFor(valueNode); 3544 | } 3545 | else if (valueNode.token.type === NOTE) { 3546 | this.visitNoteList(valueNode); 3547 | } 3548 | } 3549 | catch (ex) { 3550 | throw ex; 3551 | } 3552 | } 3553 | 3554 | visitId(node) { 3555 | let varName = node.value; 3556 | if (!this.currentScope.lookup(varName, false)) { 3557 | throw Error(`Name error in line ${node.token.lineno}: ${varName} does not exist`); 3558 | } 3559 | } 3560 | 3561 | visitFor(node) { 3562 | try { 3563 | let right = node.right; 3564 | let left = node.left; 3565 | if (left.token.type === NOTE) { 3566 | this.visitNoteList(left); 3567 | } 3568 | else if (left && left.token.type === ID) { 3569 | this.visitId(left); 3570 | } 3571 | else if (left && left.token.type === BEAT) { 3572 | this.visitBeat(left); 3573 | } 3574 | else if(left && left.token.type === CHOOSE){ 3575 | this.visitChoose(left); 3576 | } 3577 | else if(left && left.token.type === SELECT){ 3578 | this.visitSelect(left); 3579 | } 3580 | if (right && right.token.type === ID) { 3581 | this.visitId(right); 3582 | } 3583 | else if (right && right.token.type === BEAT) { 3584 | this.visitBeat(right); 3585 | } 3586 | } 3587 | catch (ex) { 3588 | throw ex; 3589 | } 3590 | } 3591 | 3592 | visitNoteList(node) { 3593 | while (node != null) { 3594 | node = node.child; 3595 | } 3596 | } 3597 | 3598 | visitNote(node) { 3599 | } 3600 | 3601 | visitBeat(node) { 3602 | } 3603 | } 3604 | return ({ 3605 | Interpreter: HandelInterpreter, 3606 | Lexer: HandelLexer, 3607 | Parser: HandelParser, 3608 | SymbolTableBuilder: SymbolTableBuilder, 3609 | }) 3610 | })(); 3611 | 3612 | export async function RunHandel(code, config) { 3613 | await Tone.start(); 3614 | try { 3615 | 3616 | if(config && config.layer){ 3617 | Tone.Transport.stop(Tone.now()); 3618 | sampleManager.cleanup(); 3619 | } 3620 | else{ 3621 | StopHandel(); 3622 | } 3623 | 3624 | const lexer = new Handel.Lexer(code); 3625 | const parser = new Handel.Parser(lexer); 3626 | 3627 | //(handle midi config in interpreter) 3628 | let midi; 3629 | if (config && config.outputMidi) { midi = new Midi() } 3630 | const interpreter = new Handel.Interpreter(parser, config, midi); 3631 | 3632 | const symTableBuilder = new Handel.SymbolTableBuilder(); 3633 | const programNode = parser.program(); 3634 | symTableBuilder.visitProgram(programNode); 3635 | interpreter.visitProgram(programNode); 3636 | } 3637 | catch (ex) { 3638 | throw ex; 3639 | } 3640 | } 3641 | 3642 | export function StopHandel() { 3643 | Tone.Transport.cancel(0); 3644 | Tone.Transport.stop(Tone.now()); 3645 | sampleManager.cleanup(); 3646 | } 3647 | 3648 | export function MakeInstrument(urls, increment) { 3649 | let sampler = new Tone.Sampler({ 3650 | urls: urls, 3651 | }).toDestination() 3652 | sampleManager.samplers.push(sampler); 3653 | return sampler 3654 | } 3655 | --------------------------------------------------------------------------------