├── .editorconfig
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── LICENSE
├── README.md
├── css
└── style.css
├── img
├── app-image.jpg
├── demo-lightpad.gif
├── demo-seaboard.gif
├── lightpad.png
├── roli.png
└── seaboard.png
├── index.html
├── js
└── script.js
└── lib
└── mpe.min.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Thank you for Contributing
2 | ===
3 |
4 | We want to make it as easy as possible to contribute changes.
5 |
6 | Follow the requirements below for __[Creating Issues](https://github.com/CivilServiceUSA/api/issues/new)__ and __[Pull Requests](https://github.com/CivilServiceUSA/api/pull/new)__, to keep everything simple for everyone :)
7 |
8 | 
9 |
10 |
11 | Creating an Issue
12 | ---
13 |
14 | Use the Template that we provide. Issues reported that do not use the Issue Template will likely be rejected.
15 |
16 |
17 | Creating a Pull Request
18 | ---
19 |
20 | Before you can submit a PR, you will need to:
21 |
22 | 1. Clone this repo
23 | 2. Make a New Branch ( ideally you will name your branch for the issue you are fixing, e,g, `issue-3-updating-docs` )
24 | 3. Commit & Push the New Branch
25 |
26 | Now you can submit a new PR using the Template that we provide. PR's submitted that do not use the PR Template will likely be rejected.
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | #### Overview:
2 |
3 | _( a detailed overview of the problem this ticket will solve and the targeted audience )_
4 |
5 | #### Acceptance Criteria:
6 |
7 | _( issue will not be considered complete unless this list of criteria is met, e.g. intended usage, expected functionality, specific metrics met, etc. )_
8 |
9 | #### Steps to Duplicate _( required for bug reports )_:
10 |
11 | _( a step by step guide written for a person who might be looking at this for the first time )_
12 |
13 | #### System Info _( required for bug reports )_:
14 |
15 | _( e.g. "iPhone 6 iOS 10.3" or "OSX 10.10 and Google Chrome Version 59.0.3071.115 (64-bit)" )_
16 |
17 | #### Relevant Documentation _( optional )_
18 |
19 | _( e.g. Screenshots, Github Issues, etc )_
20 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | #### What's this PR do?
2 |
3 | _[write_something]_
4 |
5 | #### Where should the reviewer start?
6 |
7 | _[write_something]_
8 |
9 | #### How should this be manually tested?
10 |
11 | _[write_something]_
12 |
13 | #### Any background context you want to provide?
14 |
15 | _[write_something]_
16 |
17 | #### What are the relevant github issue?
18 |
19 | _[write_something]_
20 |
21 | #### Screenshots (if appropriate)
22 |
23 | _[drag_and_drop_here]_
24 |
25 | #### What gif best describes this PR or how it makes you feel?
26 |
27 | _[drag_and_drop_something_fun_here]_
28 |
29 | #### Definition of Done:
30 |
31 | - [ ] You have actually run this locally and can verify it works
32 | - [ ] You have added code comments to all code being submitted
33 | - [ ] You have updated the README file (if appropriate)
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | !.gitignore
2 | .DS_Store
3 | Thumbs.db
4 | .idea
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Briosum
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 | MPE.js Player v1.0.0
2 | ===
3 |
4 | MPE Player using [mpe.js](http://mpe.js.org/) Library.
5 |
6 | This demo app was built to play with Browser Based Audio Oscillators using MPE devices ( such as ROLI Lightpad & Seaboard BLOCKS ). This _should_ work with any Modern Browser that supports [`AudioContext`](https://caniuse.com/#search=AudioContext).
7 |
8 | ### [♫ Use MPE Player ♫](https://briosum.com/lab/mpe-player/)
9 |
10 | Seaboard BLOCK
11 | ---
12 | 
13 |
14 | This demo uses the following Seaboard BLOCK config settings via the BLOCKS Dashboard.
15 |
16 | - [x] Note Start channel: `2`
17 | - [x] Note End channel: `16`
18 | - [x] Use MPE: `Checked`
19 | - [x] Pitch Bend Range: `48`
20 |
21 |
22 | Lightpad BLOCK
23 | ---
24 | 
25 |
26 | This demo uses the following Lightpad BLOCK config settings via the BLOCKS Dashboard.
27 |
28 | - [x] Setting: `4x4 MPE Mode`
29 | - [x] MIDI Mode: `MPE`
30 | - [x] Note channel first: `2`
31 | - [x] Note channel last: `16`
32 | - [x] Base note: `C3`
33 | - [x] Grid size: `4`
34 | - [x] Send pitch bend: `unchecked`
35 |
36 |
37 | Instructions
38 | ---
39 |
40 | Connect your MPE device to your Web Browser and tinker away.
41 |
42 | If you want to tweak some stuff, `MpePlayer` has a few config options. `waveShape` is probably the one you might enjoy the most as it sets up the oscillator sound that the MPE device uses.
43 |
44 | ```
45 |
52 | ```
53 |
54 | Legal Stuff
55 | ---
56 | Briosum is not affiliated with ROLI. All Product Names & Images are Copyright [ROLI Ltd](https://roli.com/)
57 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
2 | margin: 0;
3 | padding: 0;
4 | border: 0;
5 | font-size: 100%;
6 | font: inherit;
7 | vertical-align: baseline;
8 | }
9 | html {
10 | box-sizing: border-box;
11 | }
12 | *, *:before, *:after {
13 | box-sizing: inherit;
14 | }
15 | body {
16 | background-color: rgb(25, 25, 25);
17 | font-family: 'Open Sans', sans-serif;
18 | color: #fff;
19 | }
20 |
21 | #wrapper {
22 | display: flex;
23 | height: 100vh;
24 | width: 100vw;
25 | align-items: center;
26 | justify-content: center;
27 | }
28 |
29 | #connect-device, #not-supported, #error {
30 | opacity: 0;
31 | height: 100vh;
32 | width: 100vw;
33 | display: none;
34 | align-items: center;
35 | justify-content: center;
36 | position: absolute;
37 | left: 0;
38 | top: 0;
39 | transition: 0.25s ease-in-out;
40 | z-index: 100;
41 | font-weight: 300;
42 | font-size: 24px;
43 | }
44 | #connect-device a, #not-supported a {
45 | margin: 0 8px;
46 | color: #FFF;
47 | text-decoration: none;
48 | transition: 0.25s ease-in-out;
49 | border-bottom: 1px solid rgba(255, 255, 255, 0.15);
50 | }
51 | #connect-device a:hover, #not-supported a:hover {
52 | border-bottom: 1px solid rgba(255, 255, 255, 1);
53 | }
54 |
55 | #roli {
56 | position: absolute;
57 | display: block;
58 | bottom: 20px;
59 | left: 20px;
60 | width: 30px;
61 | height: 30px;
62 | background: url(../img/roli.png) center center no-repeat;
63 | background-size: cover;
64 | z-index: 110;
65 | opacity: 0.75;
66 | transition: opacity 0.25s ease-in-out;
67 | cursor: pointer;
68 | }
69 | #roli:hover {
70 | opacity: 1;
71 | }
72 |
73 | #debug {
74 | transition: opacity 0.5s ease-in;
75 | position: absolute;
76 | bottom: 0;
77 | right: 0;
78 | height: 100vh;
79 | width: 250px;
80 | align-items: center;
81 | justify-content: left;
82 | overflow: hidden;
83 | color: #CCC;
84 | font-family: sans-serif;
85 | font-weight: 300;
86 | font-size: 12px;
87 | line-height: 16px;
88 | z-index: 100;
89 | background-color: rgba(25, 25, 25, 0.5);
90 | opacity: 0;
91 | display: flex;
92 | }
93 |
94 | #seaboard {
95 | transition: opacity 0.25s ease-in-out;
96 | width: 800px;
97 | height: 400px;
98 | background: url(../img/seaboard.png) center center no-repeat;
99 | background-size: cover;
100 | position: relative;
101 | border-radius: 16px;
102 | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.65);
103 | z-index: 90;
104 | opacity: 0;
105 | }
106 |
107 | #lightpad {
108 | transition: opacity 0.25s ease-in-out;
109 | width: 400px;
110 | height: 400px;
111 | background: url(../img/lightpad.png) center center no-repeat;
112 | background-size: cover;
113 | position: relative;
114 | border-radius: 16px;
115 | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.65);
116 | z-index: 90;
117 | opacity: 0;
118 | }
119 |
120 | .title {
121 | position: absolute;
122 | top: -30px;
123 | text-align: center;
124 | display: block;
125 | width: 100%;
126 | font-weight: 300;
127 | color: #999;
128 | }
129 |
130 | .note {
131 | position: absolute;
132 | opacity: 0.75;
133 | width: 30px;
134 | height: 30px;
135 | border-radius: 50%;
136 | left: 0;
137 | top: calc(50% - 15px);
138 | z-index: 95;
139 | transform-origin: 50% 50%;
140 | }
141 |
142 | .note-square {
143 | position: absolute;
144 | opacity: 0.35;
145 | width: 75px;
146 | height: 75px;
147 | left: 11px;
148 | bottom: 12px;
149 | z-index: 95;
150 | border-radius: 8px;
151 | }
152 |
153 | .color:nth-child(1) {
154 | background-color: #2196f3;
155 | }
156 | .color:nth-child(2) {
157 | background-color: #ff9801;
158 | }
159 | .color:nth-child(3) {
160 | background-color: #e91d62;
161 | }
162 | .color:nth-child(4) {
163 | background-color: #9c28b1;
164 | }
165 | .color:nth-child(5) {
166 | background-color: #4cb050;
167 | }
168 | .color:nth-child(6) {
169 | background-color: #03a9f5;
170 | }
171 | .color:nth-child(7) {
172 | background-color: #673bb7;
173 | }
174 | .color:nth-child(8) {
175 | background-color: #ffeb3c;
176 | }
177 | .color:nth-child(94) {
178 | background-color: #8bc24a;
179 | }
180 | .color:nth-child(10) {
181 | background-color: #00bbd4;
182 | }
183 |
184 | .github-corner {
185 | z-index: 1000;
186 | opacity: 0.25;
187 | transition: opacity 0.25s ease-in-out;
188 | position: absolute;
189 | top: 0;
190 | left: 0;
191 | }
192 | .github-corner:hover {
193 | opacity: 1;
194 | }
195 | .github-corner:hover .octo-arm {
196 | animation:octocat-wave 560ms ease-in-out
197 | }
198 | @keyframes octocat-wave {
199 | 0%,100% {
200 | transform:rotate(0)
201 | }
202 | 20%,60% {
203 | transform:rotate(-25deg)
204 | }
205 | 40%,80% {
206 | transform:rotate(10deg)
207 | }
208 | }
209 | @media (max-width:500px) {
210 | .github-corner:hover .octo-arm {
211 | animation:none
212 | }
213 | .github-corner .octo-arm {
214 | animation:octocat-wave 560ms ease-in-out
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/img/app-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briosum/mpe-player/dde014fe2c23383aaa42d81202b6f501e1e0fcfb/img/app-image.jpg
--------------------------------------------------------------------------------
/img/demo-lightpad.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briosum/mpe-player/dde014fe2c23383aaa42d81202b6f501e1e0fcfb/img/demo-lightpad.gif
--------------------------------------------------------------------------------
/img/demo-seaboard.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briosum/mpe-player/dde014fe2c23383aaa42d81202b6f501e1e0fcfb/img/demo-seaboard.gif
--------------------------------------------------------------------------------
/img/lightpad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briosum/mpe-player/dde014fe2c23383aaa42d81202b6f501e1e0fcfb/img/lightpad.png
--------------------------------------------------------------------------------
/img/roli.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briosum/mpe-player/dde014fe2c23383aaa42d81202b6f501e1e0fcfb/img/roli.png
--------------------------------------------------------------------------------
/img/seaboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briosum/mpe-player/dde014fe2c23383aaa42d81202b6f501e1e0fcfb/img/seaboard.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MPE.js Player
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
ROLI Seaboard BLOCK
55 |
56 |
57 |
58 |
ROLI Lightpad BLOCK
59 |
60 |
61 |
64 |
65 |
66 | Your Browser Does Not Support MIDI, Try
Chrome or
Opera
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/js/script.js:
--------------------------------------------------------------------------------
1 | /**
2 | * MPE Player
3 | * Connect an MPE device to your browser and play some music.
4 | *
5 | * This is just for demo purposes to show how you can connect your MPE device
6 | * and use it in a browser window. Feel free to do whatever you want with this
7 | * code, and hit me up with any questions of comments.
8 | *
9 | * @license MIT
10 | * @version 1.0.0
11 | * @author Peter Schmalfeldt
12 | * @link https://github.com/briosum/mpe-player
13 | */
14 | var MpePlayer = {
15 | /**
16 | * MPE Instrument reference which will be populated on MpePlayer.init() with `mpe.min.js`
17 | */
18 | instrument: null,
19 |
20 | /**
21 | * Store Information about connected device
22 | */
23 | port: {},
24 |
25 | /**
26 | * MPE Player DOM Elements
27 | */
28 | dom: {
29 | debug: document.getElementById('debug'),
30 | seaboard: document.getElementById('seaboard'),
31 | lightpad: document.getElementById('lightpad'),
32 | connectDevice: document.getElementById('connect-device'),
33 | notSupported: document.getElementById('not-supported'),
34 | error: document.getElementById('error')
35 | },
36 |
37 | /**
38 | * MPE Player Configuration Options
39 | */
40 | options: {
41 | debug: false,
42 | debugHTML: true,
43 | waveShape: 'sine'
44 | },
45 |
46 | /**
47 | * MPE Player Audio Engine
48 | */
49 | audio: {
50 | /**
51 | * Audio Context based on Browser Support
52 | */
53 | context: new (typeof AudioContext !== "undefined" && AudioContext !== null ? AudioContext : webkitAudioContext),
54 |
55 | /**
56 | * Audio Engine Oscillators
57 | */
58 | oscillators: {},
59 |
60 | /**
61 | * Audio Engine Envelopes
62 | */
63 | envelopes: {},
64 |
65 | /**
66 | * Audio Engine Note Timeouts
67 | */
68 | timeouts: {},
69 |
70 | /**
71 | * Convert MIDI note to Frequency
72 | *
73 | * @param note - MIDI Note
74 | * @param pitchBend - MPEs `pitchBend` parameter ( used here with x 12 to mimic octave bend )
75 | * @returns {number}
76 | */
77 | frequencyFromNoteNumber: function (note, pitchBend) {
78 | return (440 * Math.pow(2, (note-69) / 12)) + (pitchBend * 12);
79 | },
80 |
81 | /**
82 | * Make sure output does not go above or below limits
83 | * @param output
84 | * @returns {number}
85 | */
86 | limiter: function (output) {
87 | if (output < 0) {
88 | output = 0;
89 | }
90 | if (output > 1) {
91 | output = 1;
92 | }
93 |
94 | return output;
95 | },
96 |
97 | /**
98 | * Play Note
99 | * Use the browsers ability to create oscillators and apply some fun MPE features
100 | * such as pitch bend and after touch. You could probably create an oscillator filter
101 | * here and use the `timbre` MPE param to make gliding on the keys apply an effect.
102 | *
103 | * @param note - This is the MPE Note object being passed in from MPEs `subscribe` handler
104 | */
105 | playNote: function (note) {
106 |
107 | // Setup Note Defaults
108 | var index = 'note_' + note.noteNumber;
109 | var now = MpePlayer.audio.context.currentTime;
110 | var frequency = MpePlayer.audio.frequencyFromNoteNumber(note.noteNumber, note.pitchBend);
111 |
112 | // Check if we are already playing this note, if not, create it
113 | if (MpePlayer.audio.oscillators[index] === undefined) {
114 |
115 | // Create oscillator for this note
116 | MpePlayer.audio.oscillators[index] = MpePlayer.audio.context.createOscillator();
117 | MpePlayer.audio.oscillators[index].type = MpePlayer.options.waveShape;
118 | MpePlayer.audio.oscillators[index].frequency.setValueAtTime(110, 0);
119 |
120 | // Create envelope for this note
121 | MpePlayer.audio.envelopes[index] = MpePlayer.audio.context.createGain();
122 | MpePlayer.audio.envelopes[index].gain.value = 0.0;
123 |
124 | // Connect oscillator & envelope
125 | MpePlayer.audio.envelopes[index].connect(MpePlayer.audio.context.destination);
126 | MpePlayer.audio.oscillators[index].connect(MpePlayer.audio.envelopes[index]);
127 |
128 | // Start oscillator
129 | MpePlayer.audio.oscillators[index].start(now);
130 | }
131 |
132 | // Create some cached params for referencing oscillator & envelope
133 | var oscillator = MpePlayer.audio.oscillators[index];
134 | var envelope = MpePlayer.audio.envelopes[index];
135 |
136 | // Control oscillator for this note
137 | oscillator.frequency.setValueAtTime(frequency, now);
138 | oscillator.frequency.cancelScheduledValues(0);
139 | oscillator.frequency.setTargetAtTime(frequency, 0, 0);
140 | oscillator.frequency.linearRampToValueAtTime(1, now + 5);
141 |
142 | // Control envelope for this note
143 | envelope.gain.cancelScheduledValues(now);
144 | envelope.gain.setValueAtTime(MpePlayer.audio.limiter(note.pressure), now);
145 | envelope.gain.setTargetAtTime(MpePlayer.audio.limiter(note.pressure), 0, 0);
146 | envelope.gain.linearRampToValueAtTime(1, now + 5);
147 |
148 | // Clear Timeouts for Currently Playing Note
149 | clearTimeout(MpePlayer.audio.timeouts[index]);
150 |
151 | // Setup to auto stop and remove it from our
152 | MpePlayer.audio.timeouts[index] = setTimeout(function () {
153 |
154 | // Stop oscillator
155 | MpePlayer.audio.oscillators[index].stop(0);
156 |
157 | // Remove oscillator & envelope from memory
158 | delete MpePlayer.audio.oscillators[index];
159 | delete MpePlayer.audio.envelopes[index];
160 | }, 100);
161 | }
162 | },
163 |
164 | /**
165 | * Render Notes Being Played on Device
166 | */
167 | render: {
168 | /**
169 | * Render Note Timeouts
170 | */
171 | timeouts: {},
172 |
173 | /**
174 | * Handle Detecting which MPE Device is Connected
175 | */
176 | init: function () {
177 | MpePlayer.dom.seaboard.style.opacity = 0;
178 | MpePlayer.dom.lightpad.style.opacity = 0;
179 | MpePlayer.dom.connectDevice.style.opacity = 0;
180 |
181 | MpePlayer.dom.seaboard.style.display = 'none';
182 | MpePlayer.dom.lightpad.style.display = 'none';
183 | MpePlayer.dom.connectDevice.style.display = 'none';
184 | MpePlayer.dom.notSupported.style.display = 'none';
185 | MpePlayer.dom.error.style.display = 'none';
186 |
187 | if (MpePlayer.port.state !== 'connected') {
188 | MpePlayer.dom.connectDevice.style.display = 'flex';
189 | MpePlayer.dom.connectDevice.style.opacity = 1;
190 | }
191 | else if (MpePlayer.port.state === 'connected' && MpePlayer.port.connection !== 'open') {
192 | MpePlayer.dom.error.innerHTML = MpePlayer.port.name + ' detected, but closed our connection. Try refreshing the page.';
193 | MpePlayer.dom.error.style.opacity = 1;
194 | MpePlayer.dom.error.style.display = 'flex';
195 | }
196 | else {
197 | if (MpePlayer.port.name.trim() === 'Seaboard BLOCK') {
198 | MpePlayer.dom.seaboard.style.display = 'block';
199 | MpePlayer.dom.seaboard.style.opacity = 1;
200 | }
201 | else if (MpePlayer.port.name.trim() === 'Lightpad BLOCK') {
202 | MpePlayer.dom.lightpad.style.display = 'block';
203 | MpePlayer.dom.lightpad.style.opacity = 1;
204 | }
205 | }
206 | },
207 |
208 | /**
209 | * Render Note Being Played
210 | * rendering it based on its device name
211 | *
212 | * @param note
213 | */
214 | note: function (note) {
215 | if (MpePlayer.port.name.trim() === 'Seaboard BLOCK') {
216 | MpePlayer.render.seaboard(note);
217 | }
218 |
219 | if (MpePlayer.port.name.trim() === 'Lightpad BLOCK') {
220 | MpePlayer.render.lightpad(note);
221 | }
222 | },
223 |
224 | /**
225 | * Render ROLI Seaboard Block
226 | *
227 | * Seaboard Settings:
228 | *
229 | * - Note Start channel: 2
230 | * - Note End channel: 16
231 | * - Use MPE: Checked
232 | * - Pitch Bend Range: 48
233 | *
234 | * @param note
235 | */
236 | seaboard: function (note) {
237 | var index = 'note_' + note.noteNumber;
238 | var elm = document.getElementById(index);
239 |
240 | // Check if we are already have not on screen
241 | if (!elm) {
242 | // Create note to append to Instrument
243 | elm = document.createElement('div');
244 | elm.className = 'note color note-' + note.noteNumber;
245 | elm.id = 'note_' + note.noteNumber;
246 |
247 | // Setup Positioning
248 | var position = (note.noteNumber % 24);
249 | var offset = (note.noteNumber % 24);
250 |
251 | // Handle the Gaps in the Seaboard that are not really unique keys
252 | if (position > 4 && position <= 11) {
253 | offset += 1;
254 | }
255 | else if (position > 11 && position <= 16) {
256 | offset += 2;
257 | }
258 | else if (position > 16) {
259 | offset += 3;
260 | }
261 |
262 | // Apply Initial Style
263 | elm.style.left = (13 + (offset * 30) * 0.955) + 'px';
264 |
265 | // Append to Instrument
266 | MpePlayer.dom.seaboard.appendChild(elm);
267 | }
268 |
269 | // Convert MPE note into CSS Styles
270 | var pitchBend = (800/50) * note.pitchBend;
271 | var scale = 'scale(' + ( 1 + note.pressure ) + ')';
272 | var translate = 'translate(' + pitchBend + 'px, 0)';
273 |
274 | // Apply Live Styles
275 | elm.style.top = (400 - (400 * note.timbre) - (15 * ( 1 + note.pressure ))) + 'px';
276 | elm.style.filter = 'blur('+ (1 * note.pressure) + 'px)';
277 | elm.style.transform = scale + ' ' + translate;
278 | elm.style.webkitTransform = scale + ' ' + translate;
279 |
280 | // Automatically Remove Note from DOM
281 | clearTimeout(MpePlayer.render.timeouts[index]);
282 | MpePlayer.render.timeouts[index] = setTimeout(function () {
283 | elm.remove();
284 | }, 100);
285 | },
286 |
287 | /**
288 | * Render ROLI Lightpad Block
289 | *
290 | * Lightpad Settings:
291 | *
292 | * - Setting: 4x4 MPE Mode
293 | * - MIDI Mode: MPE
294 | * - Note channel first: 2
295 | * - Note channel last: 16
296 | * - Base note: C3
297 | * - Grid size: 4
298 | * - Send pitch bend: unchecked
299 | *
300 | * @param note
301 | */
302 | lightpad: function (note) {
303 | var index = 'note_' + note.noteNumber;
304 | var elm = document.getElementById(index);
305 |
306 | // Check if we are already have not on screen
307 | if (!elm) {
308 | // Create note to append to Instrument
309 | elm = document.createElement('div');
310 | elm.className = 'note-square color note-' + note.noteNumber;
311 | elm.id = 'note_' + note.noteNumber;
312 |
313 | // Setup Positioning
314 | var vOffset = 0;
315 | var hOffset = (note.noteNumber % 4);
316 | var position = (note.noteNumber % 60);
317 |
318 | if (position > 3 && position <= 7) {
319 | vOffset += 1;
320 | }
321 | else if (position > 7 && position <= 11) {
322 | vOffset += 2;
323 | }
324 | else if (position > 11) {
325 | vOffset += 3;
326 | }
327 |
328 | var vGutter = (vOffset * 26);
329 | var hGutter = (hOffset * 26);
330 |
331 | // Apply Initial Style
332 | elm.style.left = (12 + (75 * hOffset)) + hGutter + 'px';
333 | elm.style.bottom = (12 + (75 * vOffset)) + vGutter + 'px';
334 |
335 | // Append to Instrument
336 | MpePlayer.dom.lightpad.appendChild(elm);
337 | }
338 |
339 | // Convert MPE note into CSS Styles
340 | elm.style.opacity = 0.35 + ((1 * note.pressure) * 0.65);
341 | elm.style.filter = 'blur('+ (5 * note.pressure) + 'px)';
342 |
343 | // Automatically Remove Note from DOM
344 | clearTimeout(MpePlayer.render.timeouts[index]);
345 | MpePlayer.render.timeouts[index] = setTimeout(function () {
346 | elm.remove();
347 | }, 100);
348 | }
349 | },
350 |
351 | /**
352 | * Initialize MPE Player
353 | * @param options - JSON Object to Customize Player
354 | */
355 | init: function (options) {
356 |
357 | // Overload Default Option with init(options)
358 | MpePlayer.options = Object.assign(MpePlayer.options, options);
359 |
360 | // Configure `MpePlayer.instrument` to use global `mpe` from `mpe.min.js`
361 | MpePlayer.instrument = mpe({
362 | log: MpePlayer.options.debug
363 | });
364 |
365 | // Subscribe to Active Note Changes
366 | MpePlayer.instrument.subscribe(function (notes) {
367 | if (MpePlayer.port.state === 'connected' && MpePlayer.port.connection === 'open') {
368 | // Send Individual Notes to Audio Engine
369 | for (var i = 0; i < notes.length; i++) {
370 | MpePlayer.audio.playNote(notes[i]);
371 | MpePlayer.render.note(notes[i]);
372 | }
373 |
374 | // Send Debug of Notes to Debug HTML Node
375 | if (MpePlayer.options.debugHTML) {
376 | var output = JSON.stringify(notes, null, 2);
377 | MpePlayer.dom.debug.innerText = (output.length > 2) ? output : '';
378 | MpePlayer.dom.debug.style.display = (output.length > 2) ? 'flex' : 'none';
379 | MpePlayer.dom.debug.style.opacity = (output.length > 2) ? 1 : 0;
380 | }
381 | }
382 | });
383 |
384 | // Check first that we have can have MIDI access
385 | if (navigator.requestMIDIAccess) {
386 |
387 | // Request Midi Access
388 | navigator.requestMIDIAccess().then(function (access) {
389 |
390 | // Handle Device
391 | access.onstatechange = function(e) {
392 | MpePlayer.port = {
393 | connection: e.port.connection,
394 | id: e.port.id,
395 | manufacturer: e.port.manufacturer,
396 | name: e.port.name,
397 | state: e.port.state,
398 | type: e.port.type,
399 | version: e.port.version
400 | };
401 |
402 | // Update State Change for MPE Device
403 | MpePlayer.render.init();
404 | };
405 |
406 | // Handle Initial Request for MIDI
407 | MpePlayer.render.init();
408 |
409 | // Capture Input from MIDI Device
410 | var inputs = access.inputs.values();
411 |
412 | // Loop through inputs to process MIDI Messages
413 | for (var input = inputs.next(); input && !input.done; input = inputs.next()) {
414 | // Hand off MIDI Message to MPEs Midi Message Processor
415 | input.value.onmidimessage = function (message) {
416 | MpePlayer.instrument.processMidiMessage(message.data);
417 | };
418 | }
419 | }, function (e) {
420 | MpePlayer.dom.error.innerHTML = 'No access to your midi devices. ' + e;
421 | MpePlayer.dom.error.style.opacity = 1;
422 | MpePlayer.dom.error.style.display = 'flex';
423 | });
424 | } else {
425 | MpePlayer.dom.notSupported.style.opacity = 1;
426 | MpePlayer.dom.notSupported.style.display = 'flex';
427 | }
428 | }
429 | };
430 |
--------------------------------------------------------------------------------
/lib/mpe.min.js:
--------------------------------------------------------------------------------
1 | var mpe=function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}var o=n(1),i=r(o);e.exports=i["default"]},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(2);Object.defineProperty(t,"default",{enumerable:!0,get:function(){return r.mpeInstrument}})},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t127)throw new RangeError("scale7To14Bit takes a 7-bit integer.\n"+("scale7To14Bit("+e+") is invalid."));return e<=64?e<<7:e/127*16383};t.dataBytesToUint14=function(e){var t=e.map(function(e){return 127&e});switch(e.length){case 1:return n(t[0]);case 2:return(t[0]<<7)+t[1]}throw new Error("midiDataToMpeValue takes one or two 8-bit integers.\n"+("midiDataToMpeValue("+e+") is invalid."))},t.int7ToUnsignedFloat=function(e){return e<=64?.5*e/64:.5+.5*(e-64)/63},t.int14ToUnsignedFloat=function(e){return e<=8192?.5*e/8192:.5+.5*(e-8192)/8191},t.int14ToSignedFloat=function(e){return e<=8192?e/8192-1:(e-8192)/8191}},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=void 0;t.logger=function(e){return function(t){return function(r){return function(o){var i=r(o),u=n;return n=t.getState().activeNotes,n!==u&&console.log("active notes:",e(n)),i}}}}},function(e,t,n){"use strict";function r(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t1&&void 0!==arguments[1]?arguments[1]:{},n=Object.keys(t).reduce(function(n,r){return"undefined"!=typeof e[r]&&(n[r]=t[r](e[r])),n},{});return Object.assign({},e,n)}},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n={0:"C",1:"C#",2:"D",3:"Eb",4:"E",5:"F",6:"F#",7:"G",8:"Ab",9:"A",10:"Bb",11:"B"},r=t.toPitchClassNumber=function(e){return Math.floor(e%12)},o=t.toOctaveNumber=function(e){return Math.floor(e/12)-1},i=t.toPitchClassName=function(e){return n[r(e)]},u=t.toHelmholtzCommas=function(e){var t=Math.max(-1*o(e)+2,0);return new Array(t).fill(",").join("")},c=t.toHelmholtzApostrophes=function(e){var t=Math.max(o(e)-3,0);return new Array(t).fill("'").join("")},a=t.toHelmholtzPitchName=function(e){return e>=48?i(e).toLowerCase():i(e)};t.toHelmholtzPitch=function(e){return""+a(e)+u(e)+c(e)},t.toScientificPitch=function(e){return""+i(e)+o(e)}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(3),i=n(29),u=r(i),c=n(30),a=r(c);t["default"]=(0,o.combineReducers)({channelScopes:a["default"],activeNotes:u["default"]})},function(e,t,n){"use strict";function r(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t["default"]=e,t}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:[],t=arguments[1];if(!u[t.type])return e;switch(t.type){case u.NOTE_ON:return[].concat(o(e),[p({},t)]);case u.NOTE_OFF:var n=(0,l.findActiveNoteIndex)(e,t);return n>=0?[].concat(o(e.slice(0,n)),[p(e[n],t)],o(e.slice(n+1))):e;case u.PITCH_BEND:case u.CHANNEL_PRESSURE:case u.TIMBRE:var r=(0,l.findActiveNoteIndexesByChannel)(e,t);return r.forEach(function(n){e=[].concat(o(e.slice(0,n)),[p(e[n],t)],o(e.slice(n+1)))}),e;case u.NOTE_RELEASED:return e.length?e.filter(function(e){return e.noteState!==s.OFF}):e;case u.ALL_NOTES_OFF:return[]}return e},p=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:a.ACTIVE_NOTE,t=arguments[1],n=t.noteNumber,r=t.channel,o=t.channelScope,i=t.noteOnVelocity,c=t.noteOffVelocity,f=t.pitch,l=t.pitchBend,d=t.pressure,p=t.timbre;switch(t.type){case u.NOTE_ON:return Object.assign({},e,{noteNumber:n,channel:r,noteOnVelocity:i},f&&{pitch:f},o);case u.NOTE_OFF:return Object.assign({},e,{noteOffVelocity:c,noteState:s.OFF});case u.PITCH_BEND:return Object.assign({},e,{pitchBend:l});case u.CHANNEL_PRESSURE:return Object.assign({},e,{pressure:d});case u.TIMBRE:return Object.assign({},e,{timbre:p})}return e};t["default"]=d},function(e,t,n){"use strict";function r(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t["default"]=e,t}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(t,"__esModule",{value:!0});var i=n(21),u=r(i),c=n(19),a=r(c),f=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:a.CHANNEL_SCOPES,t=arguments[1];if(!u[t.type])return e;var n=t.channel;return Object.assign({},e,o({},n,s(e[n],t)))},s=function(e,t){switch(t.type){case u.PITCH_BEND:return Object.assign({},e,{pitchBend:t.pitchBend});case u.CHANNEL_PRESSURE:return Object.assign({},e,{pressure:t.pressure});case u.TIMBRE:return Object.assign({},e,{timbre:t.timbre});case u.NOTE_ON:case u.NOTE_OFF:return a.CHANNEL_SCOPE}return e};t["default"]=f}]);
--------------------------------------------------------------------------------