├── .circleci └── config.yml ├── .flowconfig ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── flow-typed └── npm │ └── jest_v21.x.x.js ├── gallium-live ├── .babelrc ├── .gitignore ├── package.json ├── scripts │ └── publish.sh ├── src │ ├── App.js │ ├── BPMSelector.js │ ├── BPMSelector.test.js │ ├── Editor.js │ ├── Editor.test.js │ ├── Editor_test_utils.js │ ├── OutputSelector.js │ ├── OutputSelector.test.js │ ├── ToggleInvert.js │ ├── ToggleInvert.test.js │ ├── __tests__ │ │ ├── initialization.test.js │ │ └── persistence.test.js │ ├── app_actions.js │ ├── efx.js │ ├── index.html │ ├── index.js │ ├── local_storage.js │ ├── midi.js │ ├── midi.test.js │ ├── midi_actions.js │ ├── playback.js │ ├── playback.test.js │ ├── playback_test_utils.js │ ├── shim.js │ ├── static │ │ └── gallium_logo.svg │ ├── styles.js │ └── test_utils.js ├── webpack.config.js └── yarn.lock ├── gallium ├── .babelrc ├── README.md ├── package.json └── src │ ├── AST.js │ ├── __snapshots__ │ └── parser.test.js.snap │ ├── interpreter.js │ ├── interpreter.test.js │ ├── midi_utils.js │ ├── parser.js │ ├── parser.test.js │ ├── parser_combinators.js │ ├── pretty.js │ ├── pretty.test.js │ ├── printer.js │ ├── printer.test.js │ ├── resolver.js │ ├── resolver.test.js │ ├── semantics.js │ ├── semantics.test.js │ ├── top_level.js │ ├── top_level.test.js │ ├── type_checker.js │ └── types.js ├── lerna.json ├── package.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: node:8.7.0 6 | steps: 7 | - checkout 8 | - run: 9 | name: Install Dependencies 10 | command: yarn 11 | - run: 12 | name: Build packages 13 | command: yarn build 14 | - run: 15 | name: Run checks 16 | command: yarn check-all 17 | - run: 18 | name: Initialize workspace 19 | command: cp -R . /tmp/project 20 | - persist_to_workspace: 21 | root: /tmp/project 22 | paths: . 23 | 24 | publish-site: 25 | docker: 26 | - image: node:8.7.0 27 | steps: 28 | - run: 29 | name: Install awscli 30 | command: | 31 | apt-get -y -qq update 32 | apt-get -y -qq install awscli 33 | - attach_workspace: 34 | at: /tmp/project 35 | - run: 36 | name: Build and Publish gallium.live 37 | command: cd /tmp/project/gallium-live && ./scripts/publish.sh 38 | 39 | workflows: 40 | version: 2 41 | build-publish: 42 | jobs: 43 | - build 44 | - publish-site: 45 | requires: 46 | - build 47 | filters: 48 | branches: 49 | only: 50 | - master 51 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/styled-components/src/.* 3 | 4 | [include] 5 | 6 | [libs] 7 | 8 | [lints] 9 | 10 | [options] 11 | suppress_comment=\\(.\\|\n\\)*\\$ExpectError 12 | include_warnings=true 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | yarn-error.log 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Have a bug or a feature request? 2 | Look in the [issue tracker](https://github.com/sleexyz/gallium/issues) for your issue. If it isn't already there, feel free to create one. 3 | 4 | Bugs should include steps to reproduce. If you can, please link to a branch with a failing test. 5 | 6 | ## Want to contribute to development? 7 | Feel free to take on issues in the issue tracker. If you don't know what to work on, issues are prioritized on the [project board](https://github.com/sleexyz/gallium/projects/1). 8 | 9 | Then fork the repo and make a pull-request when you're ready! In-progress PRs are encouraged. 10 | 11 | 12 | ## [Code of Conduct](./CODE_OF_CONDUCT) 13 | 14 | ## Directories 15 | - [`./gallium`](./gallium) – language implementation 16 | - [`./gallium-live`](./gallium-live) – web based live coding environment 17 | - [`./gallium-relay`](./gallium-relay) - experimental server for collaboration 18 | 19 | ## Development Guide 20 | 21 | The following commands run in the root of the repo: 22 | 23 | ### Prequisites 24 | Install: 25 | - yarn 1.3.2 26 | - node v8.7.0 27 | 28 | Install package dependencies: 29 | ``` 30 | yarn 31 | ``` 32 | 33 | ### Building and Testing 34 | 35 | Build packages: 36 | 37 | ``` 38 | yarn build 39 | ``` 40 | 41 | 42 | To run all checks: 43 | 44 | ``` 45 | yarn check-all 46 | ``` 47 | 48 | 49 | To start a local development server for [gallium.live](http://gallium.live): 50 | 51 | ``` 52 | yarn start 53 | ``` 54 | 55 | A useful shell one-liner that checks your code as it changes. Uses [ag](https://github.com/ggreer/the_silver_searcher) and [entr](http://entrproject.org/): 56 | 57 | ``` 58 | ag -l | grep '.js' | entr -cdrs 'yarn flow && yarn test && yarn format' 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sean Lee 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 | ![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 2 | 3 | ![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 4 | 5 |

6 | 7 | GALLIUM 8 | 9 |

10 | 11 | ![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 12 | 13 | ![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 14 | 15 |

16 | A web-based environment for live coding music. 17 |

18 | 19 |

20 | 21 | http://gallium.live 22 | 23 |

24 | 25 | 26 | ![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 27 | 28 | ![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 29 | 30 | 31 | * [Setup](#setup) 32 | * [Tutorial](#tutorial) 33 | * [Basics](#basics) 34 | * [Multiple Channels](#multiple-channels) 35 | * [Advanced: Contextual Numeric Interpretation](#advanced-contextual-numeric-interpretation) 36 | * [Advanced: Polyphony via Stack-Inversion](#advanced-polyphony-via-stack-inversion) 37 | 38 | * [Reference](#reference) 39 | * [Basic operators](#basic-operators) 40 | * [MIDI operators](#midi-operators) 41 | * [Time operators](#time-operators) 42 | * [Contributing](#contributing) 43 | * [Acknowledgements](#acknowledgements) 44 | 45 | ![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 46 | 47 | ![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==) 48 | 49 | ## Setup 50 | You'll need two things: 51 | - A browser with WebMIDI support, like Google Chrome, open to *[gallium.live](http://gallium.live)*. 52 | - A MIDI output device. 53 | 54 | Want to send MIDI to a DAW like Ableton or Garageband? Here are instructions for setting up a virtual MIDI device: 55 | - *OSX* — [Set up an IAC bus](https://help.ableton.com/hc/en-us/articles/209774225-Using-virtual-MIDI-buses) 56 | - *Windows* — [Install loopMIDI](http://www.tobias-erichsen.de/software/loopmidi.html) 57 | - *Linux* — [Enable snd-virmidi](https://jazz-soft.net/download/Jazz-Plugin/LinuxSynth.html) 58 | 59 | You can also use Gallium directly with a hardware MIDI synth with something like a USB-MIDI cable. 60 | 61 | Don't have a DAW or synth? There are great free and open source ones, like [TiMidity++](http://timidity.sourceforge.net/) or [PureData](https://puredata.info/). 62 | 63 | 64 | ## Tutorial 65 | ### Basics 66 | Use `note` to start a stream of MIDI notes. 67 | ``` 68 | note 60 69 | ``` 70 | 71 | `note` can take multiple arguments. The following plays a C-major arpeggio with a cycle of 4 beats: 72 | 73 | ``` 74 | note 60 64 67 72 75 | ``` 76 | 77 | --- 78 | 79 | You can also write the previous arpeggio with `add`: 80 | 81 | ``` 82 | note 60 83 | add 0 4 7 12 84 | ``` 85 | 86 | What if we want the entire arpeggio to occur all within a beat? We can speed up the stream with `fast`: 87 | 88 | ``` 89 | note 60 90 | add 0 4 7 12 91 | fast 4 92 | ``` 93 | 94 | --- 95 | 96 | Transformers can run in parallel with `stack`. For example, the following plays a C-major *chord* on every beat: 97 | 98 | ``` 99 | note 60 100 | stack 101 | add 0 102 | add 4 103 | add 7 104 | ``` 105 | 106 | --- 107 | 108 | Pipes are connected together with `do`. 109 | 110 | Use `do` in conjunction with `stack` to do more complex things in parallel. For example: 111 | ``` 112 | note 60 113 | stack 114 | do 115 | fast 2 116 | add 12 117 | do 118 | fast 3 119 | add 24 120 | ``` 121 | 122 | will play a basic polyrhythm of `72`'s and `84`'s. 123 | 124 | --- 125 | 126 | In Gallium, inputs are supplied to operators either with indentation or with spacing. 127 | 128 | For example, we can write the previous expression in fewer lines of code by using parentheses: 129 | 130 | ``` 131 | note 60 132 | stack 133 | do (fast 2) (add 12) 134 | do (fast 3) (add 24) 135 | ``` 136 | 137 | We can even write the whole thing in one line! 138 | 139 | ``` 140 | do (note 60) (stack (do (fast 2) (add 12)) (do (fast 3) (add 24))) 141 | ``` 142 | --- 143 | 144 | By default, all expressions in gallium with zero indentation are chained together with an implicit `do`. In other words, 145 | ``` 146 | note 60 147 | fast 2 148 | add 3 149 | ``` 150 | with no indentation, really just means: 151 | ``` 152 | do 153 | note 60 154 | fast 2 155 | add 3 156 | ``` 157 | 158 | ### Multiple Channels 159 | 160 | You can send data to up to 16 different MIDI channels with `chan`. Channels are numbered from 0 to 15. 161 | 162 | For example, the following alternates between sending middle C to channel 0 and channel 1: 163 | 164 | 165 | ``` 166 | note 60 167 | chan 0 1 168 | ``` 169 | 170 | If you want control two channels separately, use it in conjunction with `stack`. For example: 171 | 172 | ``` 173 | stack 174 | do 175 | note 60 176 | sub 24 177 | chan 0 178 | do 179 | note 60 180 | add 0 2 5 7 181 | chan 1 182 | ``` 183 | 184 | 185 | ### Advanced: Contextual Numeric Interpretation 186 | 187 | In Gallium, numbers are interpreted differently depending on the context. 188 | 189 | What does that mean? Let's go through a practical example. Suppose we have a pattern that alternates between two notes: 190 | 191 | ``` 192 | note 60 80 193 | ``` 194 | 195 | What if we want to play the 80 twice? We can wrap the 80 in a `do` and simply add a `fast 2`: 196 | 197 | ``` 198 | note 60 (do 80 (fast 2)) 199 | ``` 200 | 201 | This is exactly equivalent to: 202 | 203 | ``` 204 | alt (note 60) (do (note 80) (fast 2)) 205 | ``` 206 | 207 | where [`alt`](./alt) is the operator that switches between pipes. 208 | 209 | --- 210 | 211 | What's going on? 212 | 213 | `note` sets an interpretation for numbers in all its subexpressions. Unless another operator overrides this interpretation (like `fast`, in our case), all numbers get interpreted with `note`. 214 | 215 | 216 | ---- 217 | 218 | All operators in Gallium that work with numbers behave similarly, including [`fast`](./fast) and [`add`](./add). See the [Reference](./Reference) section for a complete list of operators. 219 | 220 | 221 | ### Advanced: Polyphony via Stack-Inversion 222 | 223 | We can exploit contextual numeric interpretation to introduce a useful technique called *stack-inversion*, which allows concise ways to do variations on polyphony. 224 | 225 | Here is a stream of C-major triads: 226 | 227 | ``` 228 | note 60 229 | stack (add 0) (add 4) (add 7) 230 | ``` 231 | 232 | Writing the `add` three times can get a bit cumbersome. Stack-inversion allows us to write `add` just once: 233 | 234 | ``` 235 | note 60 236 | add (stack 0 4 7) 237 | ``` 238 | 239 | 240 | --- 241 | 242 | With stack-inversion, we can whip up a delay effect, which simulataneously plays a stream of notes and then shifted copies of itself: 243 | 244 | ``` 245 | shift (stack 0 0.5) 246 | ``` 247 | 248 | where [`shift`](./shift) is an operator that shifts notes in time by an offset in beats. 249 | 250 | 251 | ## Reference 252 | 253 | ### Basic operators 254 | 255 | #### i 256 | ``` 257 | i : P 258 | ``` 259 | 260 | The identity pipe. Takes the input and simply returns it. 261 | 262 | #### m 263 | ``` 264 | m : P 265 | ``` 266 | 267 | The mute pipe. Takes the input and returns nothing. 268 | 269 | #### do 270 | ``` 271 | do : ...P -> P 272 | ``` 273 | Connects pipes together. 274 | 275 | #### stack 276 | ``` 277 | stack : ...P -> P 278 | ``` 279 | Runs pipes in parallel. 280 | 281 | #### alt 282 | 283 | ``` 284 | alt : ...P -> P 285 | ``` 286 | 287 | Alternates between pipes on every beat. 288 | 289 | ### alt0, alt1, alt2, alt3, alt4, alt5, alt6 290 | 291 | ``` 292 | alt(n) : ...P -> P 293 | ``` 294 | 295 | Alternates between pipes every 2^n beats. 296 | 297 | Note alt0 is equivalent to alt. 298 | 299 | ### out0, out1, out2, out3, out4, out5, out6 300 | 301 | ``` 302 | out(n) : ...P -> P 303 | ``` 304 | 305 | Alternates between pipes every 2^n beats. Pipes perceive time 2^n times slower. 306 | 307 | Note out0 is equivalent to alt. 308 | 309 | ### in0, in1, in2, in3, in4, in5, in6 310 | 311 | ``` 312 | in(n) : ...P -> P 313 | ``` 314 | 315 | Alternates between pipes every (1/2)^n beats. Pipes perceive time 2^n times faster. 316 | 317 | Note in0 is equivalent to alt. 318 | 319 | 320 | ### MIDI Operators 321 | 322 | #### note 323 | ``` 324 | note : ...P -> P 325 | (note): Number -> P 326 | ```` 327 | 328 | Starts a new stream of MIDI notes. `note` will ignore data from the previous pipe and overwrite it with a new stream. 329 | 330 | *Alternates between pipes on every beat.* 331 | 332 | #### add 333 | ``` 334 | add: ...P -> P 335 | (add): Number -> P 336 | ```` 337 | 338 | Transposes a stream of MIDI notes up a given number of semitones. 339 | 340 | *Alternates between pipes on every beat.* 341 | 342 | #### sub 343 | ``` 344 | sub: ...P -> P 345 | (sub): Number -> P 346 | ```` 347 | 348 | Transposes a stream of MIDI notes down a given number of semitones. 349 | 350 | *Alternates between pipes on every beat.* 351 | 352 | 353 | #### chan 354 | 355 | ``` 356 | chan : ...P -> P 357 | (chan): Number -> P 358 | ```` 359 | 360 | Sets the MIDI channel. 361 | 362 | *Alternates between pipes on every beat.* 363 | 364 | 365 | #### len 366 | 367 | ``` 368 | len : ...P -> P 369 | (len): Number -> P 370 | ```` 371 | 372 | Sets the note lengths in beats. Default length is 1. 373 | 374 | *Alternates between pipes on every beat.* 375 | 376 | 377 | ### Time Operators 378 | 379 | #### fast 380 | 381 | ``` 382 | fast : ...P -> P 383 | (fast): Number -> P 384 | ```` 385 | 386 | *Alternates between pipes on every beat.* 387 | 388 | Speeds up the pattern by a given multiplier. 389 | 390 | #### slow 391 | 392 | ``` 393 | slow : ...P -> P 394 | (slow): Number -> P 395 | ```` 396 | 397 | *Alternates between pipes on every beat.* 398 | 399 | Slows down the pattern by a given multiplier. 400 | 401 | 402 | #### shift 403 | 404 | ``` 405 | shift : ...P -> P 406 | (shift): Number -> P 407 | ```` 408 | 409 | *Alternates between pipes on every beat.* 410 | 411 | Shifts the pattern forward by an offset in beats. 412 | 413 | 414 | 415 | ## Contributing 416 | Found a bug? Missing something? Want to make things happen? Please read the [Contributing](./CONTRIBUTING.md) document for more information. 417 | 418 | ## Acknowledgements 419 | 420 | Inspired by [Tidal](https://tidalcycles.org/). 421 | 422 | Thanks to [Originate](http://www.originate.com/) for sponsoring this as a 20% project! 423 | -------------------------------------------------------------------------------- /flow-typed/npm/jest_v21.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 107cf7068b8835594e97f938e8848244 2 | // flow-typed version: 8b4dd96654/jest_v21.x.x/flow_>=v0.39.x 3 | 4 | type JestMockFn, TReturn> = { 5 | (...args: TArguments): TReturn, 6 | /** 7 | * An object for introspecting mock calls 8 | */ 9 | mock: { 10 | /** 11 | * An array that represents all calls that have been made into this mock 12 | * function. Each call is represented by an array of arguments that were 13 | * passed during the call. 14 | */ 15 | calls: Array, 16 | /** 17 | * An array that contains all the object instances that have been 18 | * instantiated from this mock function. 19 | */ 20 | instances: Array 21 | }, 22 | /** 23 | * Resets all information stored in the mockFn.mock.calls and 24 | * mockFn.mock.instances arrays. Often this is useful when you want to clean 25 | * up a mock's usage data between two assertions. 26 | */ 27 | mockClear(): void, 28 | /** 29 | * Resets all information stored in the mock. This is useful when you want to 30 | * completely restore a mock back to its initial state. 31 | */ 32 | mockReset(): void, 33 | /** 34 | * Removes the mock and restores the initial implementation. This is useful 35 | * when you want to mock functions in certain test cases and restore the 36 | * original implementation in others. Beware that mockFn.mockRestore only 37 | * works when mock was created with jest.spyOn. Thus you have to take care of 38 | * restoration yourself when manually assigning jest.fn(). 39 | */ 40 | mockRestore(): void, 41 | /** 42 | * Accepts a function that should be used as the implementation of the mock. 43 | * The mock itself will still record all calls that go into and instances 44 | * that come from itself -- the only difference is that the implementation 45 | * will also be executed when the mock is called. 46 | */ 47 | mockImplementation( 48 | fn: (...args: TArguments) => TReturn 49 | ): JestMockFn, 50 | /** 51 | * Accepts a function that will be used as an implementation of the mock for 52 | * one call to the mocked function. Can be chained so that multiple function 53 | * calls produce different results. 54 | */ 55 | mockImplementationOnce( 56 | fn: (...args: TArguments) => TReturn 57 | ): JestMockFn, 58 | /** 59 | * Just a simple sugar function for returning `this` 60 | */ 61 | mockReturnThis(): void, 62 | /** 63 | * Deprecated: use jest.fn(() => value) instead 64 | */ 65 | mockReturnValue(value: TReturn): JestMockFn, 66 | /** 67 | * Sugar for only returning a value once inside your mock 68 | */ 69 | mockReturnValueOnce(value: TReturn): JestMockFn 70 | }; 71 | 72 | type JestAsymmetricEqualityType = { 73 | /** 74 | * A custom Jasmine equality tester 75 | */ 76 | asymmetricMatch(value: mixed): boolean 77 | }; 78 | 79 | type JestCallsType = { 80 | allArgs(): mixed, 81 | all(): mixed, 82 | any(): boolean, 83 | count(): number, 84 | first(): mixed, 85 | mostRecent(): mixed, 86 | reset(): void 87 | }; 88 | 89 | type JestClockType = { 90 | install(): void, 91 | mockDate(date: Date): void, 92 | tick(milliseconds?: number): void, 93 | uninstall(): void 94 | }; 95 | 96 | type JestMatcherResult = { 97 | message?: string | (() => string), 98 | pass: boolean 99 | }; 100 | 101 | type JestMatcher = (actual: any, expected: any) => JestMatcherResult; 102 | 103 | type JestPromiseType = { 104 | /** 105 | * Use rejects to unwrap the reason of a rejected promise so any other 106 | * matcher can be chained. If the promise is fulfilled the assertion fails. 107 | */ 108 | rejects: JestExpectType, 109 | /** 110 | * Use resolves to unwrap the value of a fulfilled promise so any other 111 | * matcher can be chained. If the promise is rejected the assertion fails. 112 | */ 113 | resolves: JestExpectType 114 | }; 115 | 116 | /** 117 | * Plugin: jest-enzyme 118 | */ 119 | type EnzymeMatchersType = { 120 | toBeChecked(): void, 121 | toBeDisabled(): void, 122 | toBeEmpty(): void, 123 | toBePresent(): void, 124 | toContainReact(element: React$Element): void, 125 | toHaveClassName(className: string): void, 126 | toHaveHTML(html: string): void, 127 | toHaveProp(propKey: string, propValue?: any): void, 128 | toHaveRef(refName: string): void, 129 | toHaveState(stateKey: string, stateValue?: any): void, 130 | toHaveStyle(styleKey: string, styleValue?: any): void, 131 | toHaveTagName(tagName: string): void, 132 | toHaveText(text: string): void, 133 | toIncludeText(text: string): void, 134 | toHaveValue(value: any): void, 135 | toMatchElement(element: React$Element): void, 136 | toMatchSelector(selector: string): void 137 | }; 138 | 139 | type JestExpectType = { 140 | not: JestExpectType & EnzymeMatchersType, 141 | /** 142 | * If you have a mock function, you can use .lastCalledWith to test what 143 | * arguments it was last called with. 144 | */ 145 | lastCalledWith(...args: Array): void, 146 | /** 147 | * toBe just checks that a value is what you expect. It uses === to check 148 | * strict equality. 149 | */ 150 | toBe(value: any): void, 151 | /** 152 | * Use .toHaveBeenCalled to ensure that a mock function got called. 153 | */ 154 | toBeCalled(): void, 155 | /** 156 | * Use .toBeCalledWith to ensure that a mock function was called with 157 | * specific arguments. 158 | */ 159 | toBeCalledWith(...args: Array): void, 160 | /** 161 | * Using exact equality with floating point numbers is a bad idea. Rounding 162 | * means that intuitive things fail. 163 | */ 164 | toBeCloseTo(num: number, delta: any): void, 165 | /** 166 | * Use .toBeDefined to check that a variable is not undefined. 167 | */ 168 | toBeDefined(): void, 169 | /** 170 | * Use .toBeFalsy when you don't care what a value is, you just want to 171 | * ensure a value is false in a boolean context. 172 | */ 173 | toBeFalsy(): void, 174 | /** 175 | * To compare floating point numbers, you can use toBeGreaterThan. 176 | */ 177 | toBeGreaterThan(number: number): void, 178 | /** 179 | * To compare floating point numbers, you can use toBeGreaterThanOrEqual. 180 | */ 181 | toBeGreaterThanOrEqual(number: number): void, 182 | /** 183 | * To compare floating point numbers, you can use toBeLessThan. 184 | */ 185 | toBeLessThan(number: number): void, 186 | /** 187 | * To compare floating point numbers, you can use toBeLessThanOrEqual. 188 | */ 189 | toBeLessThanOrEqual(number: number): void, 190 | /** 191 | * Use .toBeInstanceOf(Class) to check that an object is an instance of a 192 | * class. 193 | */ 194 | toBeInstanceOf(cls: Class<*>): void, 195 | /** 196 | * .toBeNull() is the same as .toBe(null) but the error messages are a bit 197 | * nicer. 198 | */ 199 | toBeNull(): void, 200 | /** 201 | * Use .toBeTruthy when you don't care what a value is, you just want to 202 | * ensure a value is true in a boolean context. 203 | */ 204 | toBeTruthy(): void, 205 | /** 206 | * Use .toBeUndefined to check that a variable is undefined. 207 | */ 208 | toBeUndefined(): void, 209 | /** 210 | * Use .toContain when you want to check that an item is in a list. For 211 | * testing the items in the list, this uses ===, a strict equality check. 212 | */ 213 | toContain(item: any): void, 214 | /** 215 | * Use .toContainEqual when you want to check that an item is in a list. For 216 | * testing the items in the list, this matcher recursively checks the 217 | * equality of all fields, rather than checking for object identity. 218 | */ 219 | toContainEqual(item: any): void, 220 | /** 221 | * Use .toEqual when you want to check that two objects have the same value. 222 | * This matcher recursively checks the equality of all fields, rather than 223 | * checking for object identity. 224 | */ 225 | toEqual(value: any): void, 226 | /** 227 | * Use .toHaveBeenCalled to ensure that a mock function got called. 228 | */ 229 | toHaveBeenCalled(): void, 230 | /** 231 | * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact 232 | * number of times. 233 | */ 234 | toHaveBeenCalledTimes(number: number): void, 235 | /** 236 | * Use .toHaveBeenCalledWith to ensure that a mock function was called with 237 | * specific arguments. 238 | */ 239 | toHaveBeenCalledWith(...args: Array): void, 240 | /** 241 | * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called 242 | * with specific arguments. 243 | */ 244 | toHaveBeenLastCalledWith(...args: Array): void, 245 | /** 246 | * Check that an object has a .length property and it is set to a certain 247 | * numeric value. 248 | */ 249 | toHaveLength(number: number): void, 250 | /** 251 | * 252 | */ 253 | toHaveProperty(propPath: string, value?: any): void, 254 | /** 255 | * Use .toMatch to check that a string matches a regular expression or string. 256 | */ 257 | toMatch(regexpOrString: RegExp | string): void, 258 | /** 259 | * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. 260 | */ 261 | toMatchObject(object: Object | Array): void, 262 | /** 263 | * This ensures that a React component matches the most recent snapshot. 264 | */ 265 | toMatchSnapshot(name?: string): void, 266 | /** 267 | * Use .toThrow to test that a function throws when it is called. 268 | * If you want to test that a specific error gets thrown, you can provide an 269 | * argument to toThrow. The argument can be a string for the error message, 270 | * a class for the error, or a regex that should match the error. 271 | * 272 | * Alias: .toThrowError 273 | */ 274 | toThrow(message?: string | Error | Class | RegExp): void, 275 | toThrowError(message?: string | Error | Class | RegExp): void, 276 | /** 277 | * Use .toThrowErrorMatchingSnapshot to test that a function throws a error 278 | * matching the most recent snapshot when it is called. 279 | */ 280 | toThrowErrorMatchingSnapshot(): void 281 | }; 282 | 283 | type JestObjectType = { 284 | /** 285 | * Disables automatic mocking in the module loader. 286 | * 287 | * After this method is called, all `require()`s will return the real 288 | * versions of each module (rather than a mocked version). 289 | */ 290 | disableAutomock(): JestObjectType, 291 | /** 292 | * An un-hoisted version of disableAutomock 293 | */ 294 | autoMockOff(): JestObjectType, 295 | /** 296 | * Enables automatic mocking in the module loader. 297 | */ 298 | enableAutomock(): JestObjectType, 299 | /** 300 | * An un-hoisted version of enableAutomock 301 | */ 302 | autoMockOn(): JestObjectType, 303 | /** 304 | * Clears the mock.calls and mock.instances properties of all mocks. 305 | * Equivalent to calling .mockClear() on every mocked function. 306 | */ 307 | clearAllMocks(): JestObjectType, 308 | /** 309 | * Resets the state of all mocks. Equivalent to calling .mockReset() on every 310 | * mocked function. 311 | */ 312 | resetAllMocks(): JestObjectType, 313 | /** 314 | * Removes any pending timers from the timer system. 315 | */ 316 | clearAllTimers(): void, 317 | /** 318 | * The same as `mock` but not moved to the top of the expectation by 319 | * babel-jest. 320 | */ 321 | doMock(moduleName: string, moduleFactory?: any): JestObjectType, 322 | /** 323 | * The same as `unmock` but not moved to the top of the expectation by 324 | * babel-jest. 325 | */ 326 | dontMock(moduleName: string): JestObjectType, 327 | /** 328 | * Returns a new, unused mock function. Optionally takes a mock 329 | * implementation. 330 | */ 331 | fn, TReturn>( 332 | implementation?: (...args: TArguments) => TReturn 333 | ): JestMockFn, 334 | /** 335 | * Determines if the given function is a mocked function. 336 | */ 337 | isMockFunction(fn: Function): boolean, 338 | /** 339 | * Given the name of a module, use the automatic mocking system to generate a 340 | * mocked version of the module for you. 341 | */ 342 | genMockFromModule(moduleName: string): any, 343 | /** 344 | * Mocks a module with an auto-mocked version when it is being required. 345 | * 346 | * The second argument can be used to specify an explicit module factory that 347 | * is being run instead of using Jest's automocking feature. 348 | * 349 | * The third argument can be used to create virtual mocks -- mocks of modules 350 | * that don't exist anywhere in the system. 351 | */ 352 | mock( 353 | moduleName: string, 354 | moduleFactory?: any, 355 | options?: Object 356 | ): JestObjectType, 357 | /** 358 | * Returns the actual module instead of a mock, bypassing all checks on 359 | * whether the module should receive a mock implementation or not. 360 | */ 361 | requireActual(moduleName: string): any, 362 | /** 363 | * Returns a mock module instead of the actual module, bypassing all checks 364 | * on whether the module should be required normally or not. 365 | */ 366 | requireMock(moduleName: string): any, 367 | /** 368 | * Resets the module registry - the cache of all required modules. This is 369 | * useful to isolate modules where local state might conflict between tests. 370 | */ 371 | resetModules(): JestObjectType, 372 | /** 373 | * Exhausts the micro-task queue (usually interfaced in node via 374 | * process.nextTick). 375 | */ 376 | runAllTicks(): void, 377 | /** 378 | * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), 379 | * setInterval(), and setImmediate()). 380 | */ 381 | runAllTimers(): void, 382 | /** 383 | * Exhausts all tasks queued by setImmediate(). 384 | */ 385 | runAllImmediates(): void, 386 | /** 387 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 388 | * or setInterval() and setImmediate()). 389 | */ 390 | runTimersToTime(msToRun: number): void, 391 | /** 392 | * Executes only the macro-tasks that are currently pending (i.e., only the 393 | * tasks that have been queued by setTimeout() or setInterval() up to this 394 | * point) 395 | */ 396 | runOnlyPendingTimers(): void, 397 | /** 398 | * Explicitly supplies the mock object that the module system should return 399 | * for the specified module. Note: It is recommended to use jest.mock() 400 | * instead. 401 | */ 402 | setMock(moduleName: string, moduleExports: any): JestObjectType, 403 | /** 404 | * Indicates that the module system should never return a mocked version of 405 | * the specified module from require() (e.g. that it should always return the 406 | * real module). 407 | */ 408 | unmock(moduleName: string): JestObjectType, 409 | /** 410 | * Instructs Jest to use fake versions of the standard timer functions 411 | * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, 412 | * setImmediate and clearImmediate). 413 | */ 414 | useFakeTimers(): JestObjectType, 415 | /** 416 | * Instructs Jest to use the real versions of the standard timer functions. 417 | */ 418 | useRealTimers(): JestObjectType, 419 | /** 420 | * Creates a mock function similar to jest.fn but also tracks calls to 421 | * object[methodName]. 422 | */ 423 | spyOn(object: Object, methodName: string): JestMockFn, 424 | /** 425 | * Set the default timeout interval for tests and before/after hooks in milliseconds. 426 | * Note: The default timeout interval is 5 seconds if this method is not called. 427 | */ 428 | setTimeout(timeout: number): JestObjectType 429 | }; 430 | 431 | type JestSpyType = { 432 | calls: JestCallsType 433 | }; 434 | 435 | /** Runs this function after every test inside this context */ 436 | declare function afterEach( 437 | fn: (done: () => void) => ?Promise, 438 | timeout?: number 439 | ): void; 440 | /** Runs this function before every test inside this context */ 441 | declare function beforeEach( 442 | fn: (done: () => void) => ?Promise, 443 | timeout?: number 444 | ): void; 445 | /** Runs this function after all tests have finished inside this context */ 446 | declare function afterAll( 447 | fn: (done: () => void) => ?Promise, 448 | timeout?: number 449 | ): void; 450 | /** Runs this function before any tests have started inside this context */ 451 | declare function beforeAll( 452 | fn: (done: () => void) => ?Promise, 453 | timeout?: number 454 | ): void; 455 | 456 | /** A context for grouping tests together */ 457 | declare var describe: { 458 | /** 459 | * Creates a block that groups together several related tests in one "test suite" 460 | */ 461 | (name: string, fn: () => void): void, 462 | 463 | /** 464 | * Only run this describe block 465 | */ 466 | only(name: string, fn: () => void): void, 467 | 468 | /** 469 | * Skip running this describe block 470 | */ 471 | skip(name: string, fn: () => void): void 472 | }; 473 | 474 | /** An individual test unit */ 475 | declare var it: { 476 | /** 477 | * An individual test unit 478 | * 479 | * @param {string} Name of Test 480 | * @param {Function} Test 481 | * @param {number} Timeout for the test, in milliseconds. 482 | */ 483 | ( 484 | name: string, 485 | fn?: (done: () => void) => ?Promise, 486 | timeout?: number 487 | ): void, 488 | /** 489 | * Only run this test 490 | * 491 | * @param {string} Name of Test 492 | * @param {Function} Test 493 | * @param {number} Timeout for the test, in milliseconds. 494 | */ 495 | only( 496 | name: string, 497 | fn?: (done: () => void) => ?Promise, 498 | timeout?: number 499 | ): void, 500 | /** 501 | * Skip running this test 502 | * 503 | * @param {string} Name of Test 504 | * @param {Function} Test 505 | * @param {number} Timeout for the test, in milliseconds. 506 | */ 507 | skip( 508 | name: string, 509 | fn?: (done: () => void) => ?Promise, 510 | timeout?: number 511 | ): void, 512 | /** 513 | * Run the test concurrently 514 | * 515 | * @param {string} Name of Test 516 | * @param {Function} Test 517 | * @param {number} Timeout for the test, in milliseconds. 518 | */ 519 | concurrent( 520 | name: string, 521 | fn?: (done: () => void) => ?Promise, 522 | timeout?: number 523 | ): void 524 | }; 525 | declare function fit( 526 | name: string, 527 | fn: (done: () => void) => ?Promise, 528 | timeout?: number 529 | ): void; 530 | /** An individual test unit */ 531 | declare var test: typeof it; 532 | /** A disabled group of tests */ 533 | declare var xdescribe: typeof describe; 534 | /** A focused group of tests */ 535 | declare var fdescribe: typeof describe; 536 | /** A disabled individual test */ 537 | declare var xit: typeof it; 538 | /** A disabled individual test */ 539 | declare var xtest: typeof it; 540 | 541 | /** The expect function is used every time you want to test a value */ 542 | declare var expect: { 543 | /** The object that you want to make assertions against */ 544 | (value: any): JestExpectType & JestPromiseType & EnzymeMatchersType, 545 | /** Add additional Jasmine matchers to Jest's roster */ 546 | extend(matchers: { [name: string]: JestMatcher }): void, 547 | /** Add a module that formats application-specific data structures. */ 548 | addSnapshotSerializer(serializer: (input: Object) => string): void, 549 | assertions(expectedAssertions: number): void, 550 | hasAssertions(): void, 551 | any(value: mixed): JestAsymmetricEqualityType, 552 | anything(): void, 553 | arrayContaining(value: Array): void, 554 | objectContaining(value: Object): void, 555 | /** Matches any received string that contains the exact expected string. */ 556 | stringContaining(value: string): void, 557 | stringMatching(value: string | RegExp): void 558 | }; 559 | 560 | // TODO handle return type 561 | // http://jasmine.github.io/2.4/introduction.html#section-Spies 562 | declare function spyOn(value: mixed, method: string): Object; 563 | 564 | /** Holds all functions related to manipulating test runner */ 565 | declare var jest: JestObjectType; 566 | 567 | /** 568 | * The global Jasmine object, this is generally not exposed as the public API, 569 | * using features inside here could break in later versions of Jest. 570 | */ 571 | declare var jasmine: { 572 | DEFAULT_TIMEOUT_INTERVAL: number, 573 | any(value: mixed): JestAsymmetricEqualityType, 574 | anything(): void, 575 | arrayContaining(value: Array): void, 576 | clock(): JestClockType, 577 | createSpy(name: string): JestSpyType, 578 | createSpyObj( 579 | baseName: string, 580 | methodNames: Array 581 | ): { [methodName: string]: JestSpyType }, 582 | objectContaining(value: Object): void, 583 | stringMatching(value: string): void 584 | }; 585 | -------------------------------------------------------------------------------- /gallium-live/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: [["env", { 3 | "targets": { 4 | "browsers": ["last 2 versions", "safari >= 7"] 5 | } 6 | }]], 7 | plugins: [ 8 | "styled-components", 9 | "transform-react-jsx", 10 | "transform-flow-strip-types", 11 | "transform-object-rest-spread", 12 | "transform-class-properties" 13 | ] 14 | } 15 | 16 | -------------------------------------------------------------------------------- /gallium-live/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /gallium-live/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gallium-live", 3 | "version": "0.3.0", 4 | "description": "gallium live", 5 | "main": "src/index.js", 6 | "repository": "https://github.com/sleexyz/gallium", 7 | "license": "MIT", 8 | "private": true, 9 | "devDependencies": { 10 | "babel-loader": "^7.1.2", 11 | "babel-plugin-styled-components": "^1.4.0", 12 | "babel-plugin-transform-class-properties": "^6.24.1", 13 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 14 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 15 | "babel-plugin-transform-react-jsx": "^6.24.1", 16 | "babel-preset-env": "^1.6.1", 17 | "copy-webpack-plugin": "^4.5.1", 18 | "enzyme": "^3.2.0", 19 | "enzyme-adapter-react-16": "^1.1.1", 20 | "html-webpack-plugin": "^2.30.1", 21 | "jest": "^21.2.1", 22 | "jest-enzyme": "^4.0.2", 23 | "performance-now": "^2.1.0", 24 | "prettier": "^1.9.2", 25 | "webpack": "^3.10.0", 26 | "webpack-dev-server": "^2.9.7" 27 | }, 28 | "jest": { 29 | "setupFiles": [ 30 | "./src/shim.js" 31 | ], 32 | "setupTestFrameworkScriptFile": "./src/test_utils.js" 33 | }, 34 | "scripts": { 35 | "build-publish": "yarn clean && webpack -p", 36 | "clean": "rm -rf ./dist", 37 | "check-all": "flow && yarn test && yarn format-check", 38 | "format": "prettier --write 'src/**/*.js'", 39 | "format-check": "prettier -l 'src/**/*.js'", 40 | "test": "jest", 41 | "start": "webpack-dev-server" 42 | }, 43 | "dependencies": { 44 | "babel-polyfill": "^6.26.0", 45 | "efx": "^0.2.2", 46 | "gallium": "^0.3.0", 47 | "react": "^16.2.0", 48 | "react-dom": "^16.2.0", 49 | "styled-components": "^2.4.0", 50 | "styled-normalize": "^2.2.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /gallium-live/scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | yarn build-publish && aws s3 sync --delete ./dist s3://gallium.live 4 | -------------------------------------------------------------------------------- /gallium-live/src/App.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | import styled from "styled-components"; 4 | import { connect, type Connect } from "./efx"; 5 | import { Editor } from "./Editor"; 6 | import { OutputSelector } from "./OutputSelector"; 7 | import { BPMSelector } from "./BPMSelector"; 8 | import { ToggleInvert } from "./ToggleInvert"; 9 | import * as Styles from "./styles"; 10 | 11 | type OwnProps = {}; 12 | 13 | type ContainerProps = { 14 | invert: boolean 15 | }; 16 | 17 | type State = { 18 | isInitialized: boolean 19 | }; 20 | 21 | const mapStateToProps = ({ invert }) => ({ invert }); 22 | 23 | export class _App extends React.Component< 24 | Connect, 25 | State 26 | > { 27 | constructor(props: *) { 28 | super(props); 29 | this.state = { isInitialized: false }; 30 | } 31 | componentDidMount() { 32 | setTimeout(() => this.setState({ isInitialized: true }), 0); 33 | } 34 | 35 | render() { 36 | return ( 37 | 41 | 42 | 43 | gallium.live 44 | 45 | 46 | source 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ); 65 | } 66 | } 67 | 68 | export const App = connect(_App, mapStateToProps); 69 | 70 | export const Container: React$ComponentType<{ 71 | isInitialized: boolean 72 | }> = styled.div` 73 | width: 100%; 74 | height: 100%; 75 | display: flex; 76 | flex-direction: column; 77 | opacity: ${props => (props.isInitialized ? 1 : 0)}; 78 | transition: opacity 500ms ease-in-out; 79 | background-color: white; 80 | `; 81 | 82 | const Pane = styled.div` 83 | flex: 0 1 auto; 84 | min-height: 50px; 85 | display: flex; 86 | justify-content: flex-end; 87 | align-items: center; 88 | padding: 0px 20px; 89 | ${Styles.transition}; 90 | opacity: 0.5; 91 | &:hover { 92 | opacity: 1; 93 | } 94 | `; 95 | 96 | const PaneChild = styled.div` 97 | padding: 10px 20px; 98 | `; 99 | 100 | const Content = styled.div` 101 | padding: 10vh 10vw; 102 | flex-grow: 1; 103 | flex-shrink: 0; 104 | display: flex; 105 | background-color: white; 106 | `; 107 | 108 | const Description = styled.div` 109 | ${Styles.text}; 110 | font-style: italic; 111 | letter-spacing: 0.25em; 112 | `; 113 | 114 | const Link = styled.a` 115 | ${Styles.text}; 116 | opacity: 0.75; 117 | &:visited { 118 | color: inherit; 119 | } 120 | `; 121 | -------------------------------------------------------------------------------- /gallium-live/src/BPMSelector.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | import { connect, type Connect } from "./efx"; 4 | import styled from "styled-components"; 5 | import * as Styles from "./styles"; 6 | import * as Playback from "./playback"; 7 | 8 | type OwnProps = {}; 9 | 10 | type ContainerProps = { 11 | bpm: number 12 | }; 13 | 14 | type State = { 15 | bpm: number 16 | }; 17 | 18 | const mapStateToProps = ({ bpm }) => ({ bpm }); 19 | 20 | class _BPMSelector extends React.Component< 21 | Connect, 22 | State 23 | > { 24 | constructor(props: *) { 25 | super(props); 26 | this.state = { bpm: props.bpm }; 27 | } 28 | 29 | onBlur = (e: *) => { 30 | this.commit(e.target.value); 31 | }; 32 | 33 | commit(bpm: number) { 34 | let value = bpm; 35 | value = Math.max(40, value); 36 | value = Math.min(800, value); 37 | this.props.dispatch(Playback.setBPM(value)); 38 | } 39 | 40 | onChange = (e: *) => { 41 | this.setState({ bpm: e.target.value }); 42 | }; 43 | 44 | onKeyPress = (e: *) => { 45 | if (e.key === "Enter") { 46 | this.commit(e.target.value); 47 | } 48 | }; 49 | 50 | componentWillReceiveProps(nextProps: *) { 51 | this.state.bpm = nextProps.bpm; 52 | } 53 | 54 | render() { 55 | return ( 56 | 57 | BPM: 58 | 65 | 66 | ); 67 | } 68 | } 69 | 70 | export const BPMSelector = connect(_BPMSelector, mapStateToProps); 71 | 72 | const Outer = styled.div` 73 | font-family: monospace; 74 | `; 75 | 76 | const Input = styled.input` 77 | ${Styles.transition}; 78 | ${Styles.box}; 79 | background: none; 80 | border: none; 81 | font-family: monospace; 82 | cursor: pointer; 83 | outline: none; 84 | color: #252525; 85 | &:active, 86 | &:hover { 87 | box-shadow: 0 0 0 1px #252525; 88 | } 89 | margin-left: 10px; 90 | `; 91 | -------------------------------------------------------------------------------- /gallium-live/src/BPMSelector.test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | import * as TestUtils from "./test_utils"; 4 | import { getBPM, setBPM } from "./playback_test_utils"; 5 | import { BPMSelector } from "./BPMSelector"; 6 | 7 | it("allows maximum bpm of 800", () => { 8 | const { wrapper, store } = TestUtils.mountWithStore(); 9 | setBPM({ wrapper, value: 900 }); 10 | expect(getBPM({ wrapper })).toBe(800); 11 | }); 12 | 13 | it("allows minimum bpm of 40", () => { 14 | const { wrapper, store } = TestUtils.mountWithStore(); 15 | setBPM({ wrapper, value: 20 }); 16 | expect(getBPM({ wrapper })).toBe(40); 17 | }); 18 | 19 | it("changes bpm on Enter keypress", () => { 20 | const { wrapper, store } = TestUtils.mountWithStore(); 21 | wrapper 22 | .find(BPMSelector) 23 | .find("input") 24 | .simulate("change", { target: { value: 120 } }); 25 | wrapper 26 | .find(BPMSelector) 27 | .find("input") 28 | .simulate("keyPress", { key: "Enter" }); 29 | expect(store.state.bpm).toBe(120); 30 | }); 31 | -------------------------------------------------------------------------------- /gallium-live/src/Editor.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | import styled from "styled-components"; 4 | import { parseTopLevel } from "gallium/lib/parser"; 5 | import { type ABT, resolve } from "gallium/lib/resolver"; 6 | import * as TopLevel from "gallium/lib/top_level"; 7 | import { silence } from "gallium/lib/semantics"; 8 | import * as MIDI from "./midi"; 9 | import * as MIDIActions from "./midi_actions"; 10 | import * as LocalStorage from "./local_storage"; 11 | import { connect, type Connect } from "./efx"; 12 | import * as Styles from "./styles"; 13 | import * as Playback from "./playback"; 14 | import * as AppActions from "./app_actions"; 15 | 16 | type OwnProps = {}; 17 | 18 | type ContainerProps = { 19 | text: string, 20 | invert: boolean 21 | }; 22 | 23 | const mapStateToProps = ({ text, invert }) => ({ text, invert }); 24 | 25 | type State = { 26 | text: string, 27 | error: ?string 28 | }; 29 | 30 | export class _Editor extends React.Component< 31 | Connect, 32 | State 33 | > { 34 | constructor(props: *) { 35 | super(props); 36 | this.state = { 37 | text: props.text, 38 | error: undefined 39 | }; 40 | } 41 | 42 | textarea: ?HTMLTextAreaElement; 43 | 44 | componentDidMount() { 45 | this.props.dispatch(AppActions.initialize()); 46 | this.updateABT(this.state.text); 47 | } 48 | 49 | componentWillUnmount() { 50 | this.props.dispatch(Playback.pause()); 51 | } 52 | 53 | onChange = (e: *) => { 54 | this.setState({ 55 | text: e.target.value 56 | }); 57 | this.updateABT(e.target.value); 58 | }; 59 | 60 | updateABT(text: string) { 61 | try { 62 | const abt = TopLevel.parseAndResolve(text); 63 | this.setState({ 64 | error: undefined 65 | }); 66 | this.props.dispatch(store => { 67 | store.state.pattern = TopLevel.interpret(abt); 68 | }); 69 | LocalStorage.saveText(text); 70 | } catch (e) { 71 | this.setState({ 72 | error: e.toString() 73 | }); 74 | } 75 | } 76 | 77 | onKeyPress = (e: SyntheticKeyboardEvent) => { 78 | if (e.key === "Enter") { 79 | e.preventDefault(); 80 | const pos = e.currentTarget.selectionStart; 81 | const prefix = this.state.text.substr(0, pos); 82 | const suffix = this.state.text.substr(pos); 83 | 84 | const prePos = prefix.lastIndexOf("\n"); 85 | const line = prefix.substring(prePos + 1); 86 | const spaceMatch = line.match(/^\ */g); 87 | if (!spaceMatch) { 88 | throw new Error("unexpected error: no match"); 89 | } 90 | const indentation = spaceMatch[0]; 91 | 92 | const extraText = "\n" + indentation; 93 | const newText = prefix + extraText + suffix; 94 | this.setState( 95 | { 96 | text: newText 97 | }, 98 | () => { 99 | (this.textarea: any).focus(); 100 | (this.textarea: any).setSelectionRange( 101 | pos + extraText.length, 102 | pos + extraText.length 103 | ); 104 | } 105 | ); 106 | } 107 | }; 108 | 109 | onTextareaRefLoad = (ref: HTMLTextAreaElement) => { 110 | this.textarea = ref; 111 | if (!this.textarea) { 112 | return; 113 | } 114 | this.textarea.focus(); 115 | }; 116 | 117 | render() { 118 | const barStyle = this.state.error ? "dotted" : "solid"; 119 | return ( 120 |