557 lines
34 KiB
JavaScript
557 lines
34 KiB
JavaScript
|
function AudioKeys(options) {
|
||
|
var self = this;
|
||
|
|
||
|
self._setState(options);
|
||
|
|
||
|
// all listeners are stored in arrays in their respective properties.
|
||
|
// e.g. self._listeners.down = [fn1, fn2, ... ]
|
||
|
self._listeners = {};
|
||
|
|
||
|
// bind DOM events
|
||
|
self._bind();
|
||
|
}
|
||
|
|
||
|
// Play well with require so that we can run a test suite and use browserify.
|
||
|
if(typeof module !== 'undefined') {
|
||
|
module.exports = AudioKeys;
|
||
|
}
|
||
|
|
||
|
AudioKeys.prototype._setState = function(options) {
|
||
|
var self = this;
|
||
|
|
||
|
if(!options) {
|
||
|
options = {};
|
||
|
}
|
||
|
|
||
|
// the state is kept in this object
|
||
|
self._state = {};
|
||
|
|
||
|
// set some defaults ...
|
||
|
self._extendState({
|
||
|
polyphony: 4,
|
||
|
rows: 1,
|
||
|
priority: 'last',
|
||
|
rootNote: 60,
|
||
|
octaveControls: true,
|
||
|
octave: 0,
|
||
|
velocityControls: true,
|
||
|
velocity: 127,
|
||
|
keys: [],
|
||
|
buffer: []
|
||
|
});
|
||
|
|
||
|
// ... and override them with options.
|
||
|
self._extendState(options);
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._extendState = function(options) {
|
||
|
var self = this;
|
||
|
|
||
|
for(var o in options) {
|
||
|
self._state[o] = options[o];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype.set = function(/* options || property, value */) {
|
||
|
var self = this;
|
||
|
|
||
|
if(arguments.length === 1) {
|
||
|
self._extendState(arguments[0]);
|
||
|
} else {
|
||
|
self._state[arguments[0]] = arguments[1];
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype.get = function(property) {
|
||
|
var self = this;
|
||
|
|
||
|
return self._state[property];
|
||
|
};
|
||
|
|
||
|
// ================================================================
|
||
|
// Event Listeners
|
||
|
// ================================================================
|
||
|
|
||
|
// AudioKeys has a very simple event handling system. Internally
|
||
|
// we'll call self._trigger('down', argument) when we want to fire
|
||
|
// an event for the user.
|
||
|
|
||
|
AudioKeys.prototype.down = function(fn) {
|
||
|
var self = this;
|
||
|
|
||
|
// add the function to our list of listeners
|
||
|
self._listeners.down = (self._listeners.down || []).concat(fn);
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype.up = function(fn) {
|
||
|
var self = this;
|
||
|
|
||
|
// add the function to our list of listeners
|
||
|
self._listeners.up = (self._listeners.up || []).concat(fn);
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._trigger = function(action /* args */) {
|
||
|
var self = this;
|
||
|
|
||
|
// if we have any listeners by this name ...
|
||
|
if(self._listeners[action] && self._listeners[action].length) {
|
||
|
// grab the arguments to pass to the listeners ...
|
||
|
var args = Array.prototype.slice.call(arguments);
|
||
|
args.splice(0, 1);
|
||
|
// and call them!
|
||
|
self._listeners[action].forEach( function(fn) {
|
||
|
fn.apply(self, args);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// ================================================================
|
||
|
// DOM Bindings
|
||
|
// ================================================================
|
||
|
|
||
|
AudioKeys.prototype._bind = function() {
|
||
|
var self = this;
|
||
|
|
||
|
if(typeof window !== 'undefined' && window.document) {
|
||
|
window.document.addEventListener('keydown', function(e) {
|
||
|
self._addKey(e);
|
||
|
});
|
||
|
window.document.addEventListener('keyup', function(e) {
|
||
|
self._removeKey(e);
|
||
|
});
|
||
|
|
||
|
var lastFocus = true;
|
||
|
setInterval( function() {
|
||
|
if(window.document.hasFocus() === lastFocus) {
|
||
|
return;
|
||
|
}
|
||
|
lastFocus = !lastFocus;
|
||
|
if(!lastFocus) {
|
||
|
self.clear();
|
||
|
}
|
||
|
}, 100);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// _map returns the midi note for a given keyCode.
|
||
|
AudioKeys.prototype._map = function(keyCode) {
|
||
|
return this._keyMap[this._state.rows][keyCode] + this._offset();
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._offset = function() {
|
||
|
return this._state.rootNote - this._keyMap[this._state.rows].root + (this._state.octave * 12);
|
||
|
};
|
||
|
|
||
|
// _isNote determines whether a keyCode is a note or not.
|
||
|
AudioKeys.prototype._isNote = function(keyCode) {
|
||
|
return !!this._keyMap[this._state.rows][keyCode];
|
||
|
};
|
||
|
|
||
|
// convert a midi note to a frequency. we assume here that _map has
|
||
|
// already been called (to account for a potential rootNote offset)
|
||
|
AudioKeys.prototype._toFrequency = function(note) {
|
||
|
return ( Math.pow(2, ( note-69 ) / 12) ) * 440.0;
|
||
|
};
|
||
|
|
||
|
// the object keys correspond to `rows`, so `_keyMap[rows]` should
|
||
|
// retrieve that particular mapping.
|
||
|
AudioKeys.prototype._keyMap = {
|
||
|
1: {
|
||
|
root: 60,
|
||
|
// starting with the 'a' key
|
||
|
65: 60,
|
||
|
87: 61,
|
||
|
83: 62,
|
||
|
69: 63,
|
||
|
68: 64,
|
||
|
70: 65,
|
||
|
84: 66,
|
||
|
71: 67,
|
||
|
89: 68,
|
||
|
72: 69,
|
||
|
85: 70,
|
||
|
74: 71,
|
||
|
75: 72,
|
||
|
79: 73,
|
||
|
76: 74,
|
||
|
80: 75,
|
||
|
186: 76,
|
||
|
222: 77
|
||
|
},
|
||
|
2: {
|
||
|
root: 60,
|
||
|
// bottom row
|
||
|
90: 60,
|
||
|
83: 61,
|
||
|
88: 62,
|
||
|
68: 63,
|
||
|
67: 64,
|
||
|
86: 65,
|
||
|
71: 66,
|
||
|
66: 67,
|
||
|
72: 68,
|
||
|
78: 69,
|
||
|
74: 70,
|
||
|
77: 71,
|
||
|
188: 72,
|
||
|
76: 73,
|
||
|
190: 74,
|
||
|
186: 75,
|
||
|
191: 76,
|
||
|
// top row
|
||
|
81: 72,
|
||
|
50: 73,
|
||
|
87: 74,
|
||
|
51: 75,
|
||
|
69: 76,
|
||
|
82: 77,
|
||
|
53: 78,
|
||
|
84: 79,
|
||
|
54: 80,
|
||
|
89: 81,
|
||
|
55: 82,
|
||
|
85: 83,
|
||
|
73: 84,
|
||
|
57: 85,
|
||
|
79: 86,
|
||
|
48: 87,
|
||
|
80: 88,
|
||
|
219: 89,
|
||
|
187: 90,
|
||
|
221: 91
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// ================================================================
|
||
|
// KEY BUFFER
|
||
|
// ================================================================
|
||
|
|
||
|
// The process is:
|
||
|
|
||
|
// key press
|
||
|
// add to self._state.keys
|
||
|
// (an accurate representation of keys currently pressed)
|
||
|
// resolve self.buffer
|
||
|
// based on polyphony and priority, determine the notes
|
||
|
// that get triggered for the user
|
||
|
|
||
|
AudioKeys.prototype._addKey = function(e) {
|
||
|
var self = this;
|
||
|
// if the keyCode is one that can be mapped and isn't
|
||
|
// already pressed, add it to the key object.
|
||
|
if(self._isNote(e.keyCode) && !self._isPressed(e.keyCode)) {
|
||
|
var newKey = self._makeNote(e.keyCode);
|
||
|
// add the newKey to the list of keys
|
||
|
self._state.keys = (self._state.keys || []).concat(newKey);
|
||
|
// reevaluate the active notes based on our priority rules.
|
||
|
// give it the new note to use if there is an event to trigger.
|
||
|
self._update();
|
||
|
} else if(self._isSpecialKey(e.keyCode)) {
|
||
|
self._specialKey(e.keyCode);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._removeKey = function(e) {
|
||
|
var self = this;
|
||
|
// if the keyCode is active, remove it from the key object.
|
||
|
if(self._isPressed(e.keyCode)) {
|
||
|
var keyToRemove;
|
||
|
for(var i = 0; i < self._state.keys.length; i++) {
|
||
|
if(self._state.keys[i].keyCode === e.keyCode) {
|
||
|
keyToRemove = self._state.keys[i];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// remove the key from _keys
|
||
|
self._state.keys.splice(self._state.keys.indexOf(keyToRemove), 1);
|
||
|
self._update();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._isPressed = function(keyCode) {
|
||
|
var self = this;
|
||
|
|
||
|
if(!self._state.keys || !self._state.keys.length) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for(var i = 0; i < self._state.keys.length; i++) {
|
||
|
if(self._state.keys[i].keyCode === keyCode) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
// turn a key object into a note object for the event listeners.
|
||
|
AudioKeys.prototype._makeNote = function(keyCode) {
|
||
|
var self = this;
|
||
|
return {
|
||
|
keyCode: keyCode,
|
||
|
note: self._map(keyCode),
|
||
|
frequency: self._toFrequency( self._map(keyCode) ),
|
||
|
velocity: self._state.velocity
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// clear any active notes
|
||
|
AudioKeys.prototype.clear = function() {
|
||
|
var self = this;
|
||
|
// trigger note off for the notes in the buffer before
|
||
|
// removing them.
|
||
|
self._state.buffer.forEach( function(key) {
|
||
|
self._trigger('up', key);
|
||
|
});
|
||
|
self._state.keys = [];
|
||
|
self._state.buffer = [];
|
||
|
};
|
||
|
|
||
|
// ================================================================
|
||
|
// NOTE BUFFER
|
||
|
// ================================================================
|
||
|
|
||
|
// every time a change is made to _keys due to a key on or key off
|
||
|
// we need to call `_update`. It compares the `_keys` array to the
|
||
|
// `buffer` array, which is the array of notes that are really
|
||
|
// being played, makes the necessary changes to `buffer` and
|
||
|
// triggers any events that need triggering.
|
||
|
|
||
|
AudioKeys.prototype._update = function() {
|
||
|
var self = this;
|
||
|
|
||
|
// a key has been added to self._state.keys.
|
||
|
// stash the old buffer
|
||
|
var oldBuffer = self._state.buffer;
|
||
|
// set the new priority in self.state._keys
|
||
|
self._prioritize();
|
||
|
// compare the buffers and trigger events based on
|
||
|
// the differences.
|
||
|
self._diff(oldBuffer);
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._diff = function(oldBuffer) {
|
||
|
var self = this;
|
||
|
|
||
|
// if it's not in the OLD buffer, it's a note ON.
|
||
|
// if it's not in the NEW buffer, it's a note OFF.
|
||
|
|
||
|
var oldNotes = oldBuffer.map( function(key) {
|
||
|
return key.keyCode;
|
||
|
});
|
||
|
|
||
|
var newNotes = self._state.buffer.map( function(key) {
|
||
|
return key.keyCode;
|
||
|
});
|
||
|
|
||
|
// check for old (removed) notes
|
||
|
var notesToRemove = [];
|
||
|
oldNotes.forEach( function(key) {
|
||
|
if(newNotes.indexOf(key) === -1) {
|
||
|
notesToRemove.push(key);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// check for new notes
|
||
|
var notesToAdd = [];
|
||
|
newNotes.forEach( function(key) {
|
||
|
if(oldNotes.indexOf(key) === -1) {
|
||
|
notesToAdd.push(key);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
notesToAdd.forEach( function(key) {
|
||
|
for(var i = 0; i < self._state.buffer.length; i++) {
|
||
|
if(self._state.buffer[i].keyCode === key) {
|
||
|
self._trigger('down', self._state.buffer[i]);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
notesToRemove.forEach( function(key) {
|
||
|
// these need to fire the entire object
|
||
|
for(var i = 0; i < oldBuffer.length; i++) {
|
||
|
if(oldBuffer[i].keyCode === key) {
|
||
|
self._trigger('up', oldBuffer[i]);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._prioritize = function() {
|
||
|
var self = this;
|
||
|
|
||
|
// if all the keys have been turned off, no need
|
||
|
// to do anything here.
|
||
|
if(!self._state.keys.length) {
|
||
|
self._state.buffer = [];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
if(self._state.polyphony >= self._state.keys.length) {
|
||
|
// every note is active
|
||
|
self._state.keys = self._state.keys.map( function(key) {
|
||
|
key.isActive = true;
|
||
|
return key;
|
||
|
});
|
||
|
} else {
|
||
|
// set all keys to inactive.
|
||
|
self._state.keys = self._state.keys.map( function(key) {
|
||
|
key.isActive = false;
|
||
|
return key;
|
||
|
});
|
||
|
|
||
|
self['_' + self._state.priority]();
|
||
|
}
|
||
|
|
||
|
// now take the isActive keys and set the new buffer.
|
||
|
self._state.buffer = [];
|
||
|
|
||
|
self._state.keys.forEach( function(key) {
|
||
|
if(key.isActive) {
|
||
|
self._state.buffer.push(key);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// done.
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._last = function() {
|
||
|
var self = this;
|
||
|
// set the last bunch to active based on the polyphony.
|
||
|
for(var i = self._state.keys.length - self._state.polyphony; i < self._state.keys.length; i++) {
|
||
|
self._state.keys[i].isActive = true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._first = function() {
|
||
|
var self = this;
|
||
|
// set the last bunch to active based on the polyphony.
|
||
|
for(var i = 0; i < self._state.polyphony; i++) {
|
||
|
self._state.keys[i].isActive = true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._highest = function() {
|
||
|
var self = this;
|
||
|
// get the highest notes and set them to active
|
||
|
var notes = self._state.keys.map( function(key) {
|
||
|
return key.note;
|
||
|
});
|
||
|
|
||
|
notes.sort( function(b,a) {
|
||
|
if(a === b) {
|
||
|
return 0;
|
||
|
}
|
||
|
return a < b ? -1 : 1;
|
||
|
});
|
||
|
|
||
|
notes.splice(self._state.polyphony, Number.MAX_VALUE);
|
||
|
|
||
|
self._state.keys.forEach( function(key) {
|
||
|
if(notes.indexOf(key.note) !== -1) {
|
||
|
key.isActive = true;
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._lowest = function() {
|
||
|
var self = this;
|
||
|
// get the lowest notes and set them to active
|
||
|
var notes = self._state.keys.map( function(key) {
|
||
|
return key.note;
|
||
|
});
|
||
|
|
||
|
notes.sort( function(a,b) {
|
||
|
if(a === b) {
|
||
|
return 0;
|
||
|
}
|
||
|
return a < b ? -1 : 1;
|
||
|
});
|
||
|
|
||
|
notes.splice(self._state.polyphony, Number.MAX_VALUE);
|
||
|
|
||
|
self._state.keys.forEach( function(key) {
|
||
|
if(notes.indexOf(key.note) !== -1) {
|
||
|
key.isActive = true;
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// This file maps special keys to the state— octave shifting and
|
||
|
// velocity selection, both available when `rows` = 1.
|
||
|
|
||
|
AudioKeys.prototype._isSpecialKey = function(keyCode) {
|
||
|
return (this._state.rows === 1 && this._specialKeyMap[keyCode]);
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._specialKey = function(keyCode) {
|
||
|
var self = this;
|
||
|
if(self._specialKeyMap[keyCode].type === 'octave' && self._state.octaveControls) {
|
||
|
// shift the state of the `octave`
|
||
|
self._state.octave += self._specialKeyMap[keyCode].value;
|
||
|
} else if(self._specialKeyMap[keyCode].type === 'velocity' && self._state.velocityControls) {
|
||
|
// set the `velocity` to a new value
|
||
|
self._state.velocity = self._specialKeyMap[keyCode].value;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
AudioKeys.prototype._specialKeyMap = {
|
||
|
// octaves
|
||
|
90: {
|
||
|
type: 'octave',
|
||
|
value: -1
|
||
|
},
|
||
|
88: {
|
||
|
type: 'octave',
|
||
|
value: 1
|
||
|
},
|
||
|
// velocity
|
||
|
49: {
|
||
|
type: 'velocity',
|
||
|
value: 1
|
||
|
},
|
||
|
50: {
|
||
|
type: 'velocity',
|
||
|
value: 14
|
||
|
},
|
||
|
51: {
|
||
|
type: 'velocity',
|
||
|
value: 28
|
||
|
},
|
||
|
52: {
|
||
|
type: 'velocity',
|
||
|
value: 42
|
||
|
},
|
||
|
53: {
|
||
|
type: 'velocity',
|
||
|
value: 56
|
||
|
},
|
||
|
54: {
|
||
|
type: 'velocity',
|
||
|
value: 70
|
||
|
},
|
||
|
55: {
|
||
|
type: 'velocity',
|
||
|
value: 84
|
||
|
},
|
||
|
56: {
|
||
|
type: 'velocity',
|
||
|
value: 98
|
||
|
},
|
||
|
57: {
|
||
|
type: 'velocity',
|
||
|
value: 112
|
||
|
},
|
||
|
48: {
|
||
|
type: 'velocity',
|
||
|
value: 127
|
||
|
},
|
||
|
};
|
||
|
|
||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkF1ZGlvS2V5cy5qcyIsIkF1ZGlvS2V5cy5zdGF0ZS5qcyIsIkF1ZGlvS2V5cy5ldmVudHMuanMiLCJBdWRpb0tleXMubWFwcGluZy5qcyIsIkF1ZGlvS2V5cy5idWZmZXIuanMiLCJBdWRpb0tleXMucHJpb3JpdHkuanMiLCJBdWRpb0tleXMuc3BlY2lhbC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNqQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDckRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDN0pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNwR0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJhdWRpb2tleXMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJmdW5jdGlvb
|