UNPKG

webdaw-modules

Version:

a set of modules for building a web-based DAW

1,593 lines (1,324 loc) 54.2 kB
function util() { 'use strict'; var // import context, // defined in open_module.js slice = Array.prototype.slice, mPow = Math.pow, mRound = Math.round, mFloor = Math.floor, mRandom = Math.random, // floor = function(value){ // return value | 0; // }, noteLengthNames = { 1: 'quarter', 2: 'eighth', 4: 'sixteenth', 8: '32th', 16: '64th' }, foundItem, foundFolder; function typeString(o) { if (typeof o != 'object') { return typeof o; } if (o === null) { return 'null'; } //object, array, function, date, regexp, string, number, boolean, error var internalClass = Object.prototype.toString.call(o).match(/\[object\s(\w+)\]/)[1]; return internalClass.toLowerCase(); } function getNiceTime(millis) { var h, m, s, ms, seconds, timeAsString = ''; seconds = millis / 1000; // → millis to seconds h = floor(seconds / (60 * 60)); m = floor((seconds % (60 * 60)) / 60); s = floor(seconds % (60)); ms = round((seconds - (h * 3600) - (m * 60) - s) * 1000); timeAsString += h + ':'; timeAsString += m < 10 ? '0' + m : m; timeAsString += ':'; timeAsString += s < 10 ? '0' + s : s; timeAsString += ':'; timeAsString += ms === 0 ? '000' : ms < 10 ? '00' + ms : ms < 100 ? '0' + ms : ms; //console.log(h, m, s, ms); return { hour: h, minute: m, second: s, millisecond: ms, timeAsString: timeAsString, timeAsArray: [h, m, s, ms] }; } function clone(obj) { var attr, copy; if (obj === null || typeof obj !== 'object') { return obj; } copy = obj.constructor(); for (attr in obj) { if (obj.hasOwnProperty(attr)) { copy[attr] = clone(obj[attr]); } } return copy; } function copyObject(obj) { var prop, copy = {}; if (typeString(obj) !== 'object') { return {}; } for (prop in obj) { if (obj.hasOwnProperty(prop)) { copy[prop] = obj[prop]; } } return copy; } function copyName(name) { var i = name.indexOf('_copy'), copy, numCopies; if (i === -1) { copy = name + '_copy'; } else { numCopies = name.substring(i + 5); if (numCopies === '') { copy = name + '2'; } else { copy = name.slice(0, -1) + (parseInt(numCopies, 10) + 1); } } return copy; } function removeFromArray(tobeRemoved, array) { var i, j, maxi, maxj, newArray = [], remove, elementA, elementB; if (typeString(tobeRemoved) !== 'array') { tobeRemoved = [tobeRemoved]; } maxi = array.length; maxj = tobeRemoved.length; for (i = 0; i < maxi; i++) { elementA = array[i]; remove = false; for (j = 0; j < maxj; j++) { elementB = tobeRemoved[j]; if (elementA === elementB) { remove = true; break; } } if (remove === false) { newArray.push(elementA); } } return newArray; } function removeFromArray2(array, callback) { var i, maxi = array.length, element, newArray = []; for (i = 0; i < maxi; i++) { element = array[i]; if (callback(element) === false) { newArray.push(element); } } return newArray; } function round(value, decimals) { if (decimals === undefined || decimals <= 0) { return mRound(value); } var p = mPow(10, decimals); //console.log(p, decimals) return mRound(value * p) / p; } function floor(value, decimals) { if (decimals === undefined || decimals <= 0) { return mFloor(value); } var p = mPow(10, decimals); //console.log(p,decimals) return mFloor(value * p) / p; } function isEmptyObject(obj, ignore_keys) { //console.log('empty',obj) if (obj === undefined) { return false; } var i, isEmpty = true; ignore_keys = ignore_keys || ''; for (i in obj) { //console.log(i, ignore_keys.indexOf(i)); if (obj.hasOwnProperty(i) && ignore_keys.indexOf(i) === -1) { isEmpty = false; break; } } return isEmpty; //return Object.getOwnPropertyNames(obj).length === 0; } function objectForEach(o, cb) { var name, obj = o; for (name in obj) { if (obj.hasOwnProperty(name)) { //cb.call(this, obj[name], name); cb(obj[name], name); } } } function objectToArray(obj) { var i, a = []; for (i in obj) { if (obj.hasOwnProperty(i)) { a.push(obj[i]); } } return a; } function arrayToObject(arr, property) { var i, o = {}; for (i = arr.length - 1; i >= 0; i--) { o[arr[i][property]] = arr[i]; } return o; } function createClass(parent, constructor) { var thisClass; // class constructor thisClass = function () { this.parent = parent; if (arguments.length > 0) { parent.apply(this, arguments); if (constructor !== undefined) { constructor.apply(this, arguments); } } }; // inheritance thisClass.prototype = Object.create(parent.prototype); return thisClass; } function ajax(config) { var request = new XMLHttpRequest(), method = config.method === undefined ? 'GET' : config.method, fileSize, promise; function executor(resolve, reject) { reject = reject || function () { }; resolve = resolve || function () { }; request.onload = function () { if (request.status !== 200) { reject(request.status); return; } if (config.responseType === 'json') { fileSize = request.response.length; resolve(JSON.parse(request.response), fileSize); } else { resolve(request.response); } }; request.onerror = function (e) { config.onError(e); }; request.open(method, config.url, true); if (config.overrideMimeType) { request.overrideMimeType(config.overrideMimeType); } if (config.responseType) { if (config.responseType === 'json') { request.responseType = 'text'; } else { request.responseType = config.responseType; } } if (method === 'POST') { request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); } if (config.data) { request.send(config.data); } else { request.send(); } } promise = new Promise(executor); //console.log(promise); if (config.onSuccess !== undefined) { promise.then(config.onSuccess, config.onError); } else { return promise; } } function ajax2(config) { var request = new XMLHttpRequest(), method = config.method === undefined ? 'GET' : config.method, fileSize; request.onreadystatechange = function () { if (request.request === 404) { //console.error(config.url, '404'); config.onError(404); } }; request.onload = function () { if (request.status !== 200) { //console.error(config.url, request.status); config.onError(request.status); return; } // this doesn't work with gzip server compression! //fileSize = round(request.getResponseHeader('Content-Length')/1024/1024, 2); //console.log(fileSize); //console.log(config.url, request.getResponseHeader('Content-Length')); //console.log(sequencer.os, request.response); //if(sequencer.os === 'ios' && config.responseType === 'json'){ if (config.responseType === 'json') { //fileSize = round(request.response.length/1024/1024, 2); fileSize = request.response.length; //console.log(config.url, fileSize) config.onSuccess(JSON.parse(request.response), fileSize); //config.onSuccess(JSON.parse(request.response)); } else { //config.onSuccess(request.response, fileSize); config.onSuccess(request.response); } }; request.onerror = function (e) { //console.error(e); config.onError(e); }; /* request.onreadystatechange = function() { if (success !== undefined && xmlhttp.readyState === 4 && xmlhttp.status === 200) { success(request.responseText); } else if (error !== undefined ) { error(request); } }; */ request.open(method, config.url, true); if (config.overrideMimeType) { request.overrideMimeType(config.overrideMimeType); } if (config.responseType) { //console.log(config.responseType, config.url); //request.setRequestHeader('Content-type', 'application/' + config.responseType); //if(sequencer.os === 'ios' && config.responseType === 'json'){ if (config.responseType === 'json') { request.responseType = 'text'; } else { request.responseType = config.responseType; } //request.setRequestHeader('Content-type', config.responseType); } if (method === 'POST') { request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); } if (config.data) { request.send(config.data); } else { request.send(); } } function loop2(root, id, indent) { var i, tmp; for (i in root) { if (foundFolder !== false) { return; } if (root.hasOwnProperty(i)) { tmp = root[i]; if (tmp !== undefined && tmp.className === 'Folder') { //console.log(indent, i, id); if (i === id) { foundFolder = tmp; return; } else { loop2(tmp, id, indent + '.'); } } } } } function loop3(folder, items, search_subfolders, indent) { var i, item; for (i in folder) { if (folder.hasOwnProperty(i)) { if (i === 'id' || i === 'path' || i === 'className') { continue; } item = folder[i]; if (item === undefined) { continue; } //console.log(indent, i, item, item.className); if (item.className === 'Folder') { if (search_subfolders === true) { loop3(item, items, search_subfolders, indent + '.'); } } else { // loaded samples are audio object so they don't have a name, we use the key of the storage for name if (item.name === undefined) { items.push({ name: i, data: item }); } else { items.push(item); } } } } } function findItemsInFolder(path, root, search_subfolders) { search_subfolders = search_subfolders === undefined ? true : search_subfolders; var folders = pathToArray(path), numFolders = folders.length, currentFolder, i, folder, searchFolder = folders[numFolders - 1], items = []; if (numFolders === 0) { // return all items in root folder (for instance sequencer.storage.midi) loop3(root, items, search_subfolders, '.'); } else { currentFolder = root; for (i = 0; i < numFolders; i++) { folder = folders[i]; currentFolder = currentFolder[folder]; if (currentFolder === undefined) { break; } } //console.log(root, currentFolder); if (currentFolder) { loop3(currentFolder, items, search_subfolders, '.'); } else { // declared on top of this file foundFolder = false; loop2(root, searchFolder, '.'); loop3(foundFolder, items, search_subfolders, '.'); } } items.sort(function (a, b) { var nameA = a.name.toLowerCase(), nameB = b.name.toLowerCase(); if (nameA < nameB) { //sort string ascending return -1; } else if (nameA > nameB) { return 1; } return 0; //default return value (no sorting) }); return items; } function loop(obj, id, indent) { var i, tmp, type; for (i in obj) { if (foundItem !== false) { return; } if (obj.hasOwnProperty(i)) { tmp = obj[i]; type = typeString(tmp); //console.log(indent, i, id, tmp, type, tmp.className) if (i === id) { foundItem = tmp; break; } //console.log(tmp); // tmp can be null if the sample has not been loaded! if (tmp !== undefined && tmp.className === 'Folder') { indent = indent + '.'; loop(tmp, id, indent); } } } } function findItem(path, root, exact_match) { exact_match = exact_match === undefined ? false : exact_match; if (path === undefined || path === '') { return root; } //console.log('findItem', path); var i, folder, folders, numFolders, currentFolder, item, itemId; folders = pathToArray(path); itemId = folders.pop(); numFolders = folders.length; //console.log(folders, itemId); if (itemId === '') { return root; } // declared on top of util.js foundItem = false; if (folders.length > 0) { currentFolder = root; for (i = 0; i < numFolders; i++) { folder = folders[i]; currentFolder = currentFolder[folder]; if (currentFolder === undefined) { break; } } //console.log(root, currentFolder); if (currentFolder) { item = currentFolder[itemId]; } } if (item === undefined) { if (exact_match === true) { item = root[itemId]; } else { loop(root, itemId, '.'); item = foundItem; } } //console.log(item, itemId, exact_match); //console.log('found', root.id, folders, itemId, item); if (item === undefined) { item = false; } return item; } function storeItem(item, path, root) { var folder, folders, numFolders, currentFolder, i, pathString = ''; folders = pathToArray(path); numFolders = folders.length; currentFolder = root; for (i = 0; i < numFolders; i++) { folder = folders[i]; pathString += '/' + folder; //console.log(folder); if (currentFolder[folder] === undefined) { currentFolder[folder] = { path: pathString, className: 'Folder' }; } if (i === numFolders - 1) { currentFolder[folder] = item; break; } currentFolder = currentFolder[folder]; } } // -> classical/mozart/sonatas/early function deleteItem(path, root) { var item, itemId, i, obj = root; // for deleting items you need to specify the complete path, hence the 3rd argument is set to true //console.log('deleteItem', path); item = findItem(path, root, true); /* // what was this for, because it doesn't work when deleting samples (as AudioBuffer) from storage.audio: item = findItem(path, root); console.log(item); path = item.folder + '/' + item.name; console.log(path); */ if (!item) { return false; } else if (item.className === 'Folder') { // remove files in folder for (i in item) { if (item.hasOwnProperty(i)) { if (i !== 'className') { delete item[i]; } } } } path = pathToArray(path); while (path.length > 1) { i = 0; obj = root; while (i < path.length - 1) { //console.log(path[i],obj); obj = obj[path[i++]]; } //console.log(obj); itemId = path[i]; item = obj[itemId]; if (item.className === 'Folder') { if (isEmptyObject(item, 'path className')) { delete obj[itemId]; //console.log('deleting empty folder', itemId); } } else { delete obj[itemId]; //console.log('deleting item', itemId); } path.pop(); } //console.log(path, path[0] === '', root[path[0]]); if (path.length === 1 && path[0] !== '') { itemId = path[0]; item = root[itemId]; //console.log(path, path.length, itemId); if (item.className === 'Folder') { if (isEmptyObject(root[itemId], 'path className')) { delete root[itemId]; //console.log('deleting empty folder', itemId, '(2)'); } } else { delete root[itemId]; //console.log('deleting item', itemId, '(2)'); } } return true; } function parseSample(id, sample) { return new Promise(function (resolve, reject) { try { context.decodeAudioData(sample, function onSuccess(buffer) { //console.log(id, buffer); resolve({ 'id': id, 'buffer': buffer }); }, function onError(e) { console.log('error decoding audiodata', id, e); //reject(e); // don't use reject because we don't want the parent promise to reject resolve({ 'id': id, 'buffer': undefined }); } ); } catch (e) { console.log('error decoding audiodata', id, e); //reject(e); resolve({ 'id': id, 'buffer': undefined }); } }); } function loadAndParseSample(id, url) { return new Promise(function (resolve, reject) { ajax({ url: url, responseType: 'arraybuffer' }).then( function onFulfilled(data) { parseSample(id, data).then(resolve, reject); }, function onRejected() { resolve({ 'id': id, 'buffer': undefined }); } ); }); } function parseSamples(mapping) { var key, sample, promises = []; for (key in mapping) { if (mapping.hasOwnProperty(key)) { sample = mapping[key]; if (sample.indexOf('http://') === -1) { promises.push(parseSample(key, base64ToBinary(sample))); } else { promises.push(loadAndParseSample(key, sample)); } } } return new Promise(function (resolve, reject) { Promise.all(promises).then( function onFulfilled(values) { var mapping = {}; values.forEach(function (value) { mapping[value.id] = value.buffer; }); resolve(mapping); }, function onRejected(e) { reject(e); } ); }); } // use xhr.overrideMimeType('text/plain; charset=x-user-defined'); // all credits: https://github.com/gasman/jasmid function toBinaryString(input) { /* munge input into a binary string */ var t, ff, mx, scc, z; t = input || ''; ff = []; mx = t.length; scc = String.fromCharCode; for (z = 0; z < mx; z++) { ff[z] = scc(t.charCodeAt(z) & 255); } return ff.join(''); } function toUint8Array(input) { /* munge input into a binary string */ var t, uint, mx, scc, z; t = input || ''; mx = t.length; uint = new Uint8Array(mx); scc = String.fromCharCode; for (z = 0; z < mx; z++) { uint[z] = scc(t.charCodeAt(z) & 255); } return uint; } // adapted version of https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js function base64ToBinary(input) { var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', bytes, uarray, buffer, lkey1, lkey2, chr1, chr2, chr3, enc1, enc2, enc3, enc4, i, j = 0; bytes = Math.ceil((3 * input.length) / 4.0); buffer = new ArrayBuffer(bytes); uarray = new Uint8Array(buffer); lkey1 = keyStr.indexOf(input.charAt(input.length - 1)); lkey2 = keyStr.indexOf(input.charAt(input.length - 1)); if (lkey1 == 64) bytes--; //padding chars, so skip if (lkey2 == 64) bytes--; //padding chars, so skip input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); for (i = 0; i < bytes; i += 3) { //get the 3 octects in 4 ascii chars enc1 = keyStr.indexOf(input.charAt(j++)); enc2 = keyStr.indexOf(input.charAt(j++)); enc3 = keyStr.indexOf(input.charAt(j++)); enc4 = keyStr.indexOf(input.charAt(j++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; uarray[i] = chr1; if (enc3 != 64) uarray[i + 1] = chr2; if (enc4 != 64) uarray[i + 2] = chr3; } //console.log(buffer); return buffer; } function pathToArray(path) { if (path === undefined) { return []; } //console.log('path', path); path = path.replace(/undefined/g, ''); path = path.replace(/\/{2,}/g, '/'); path = path.replace(/^\//, ''); path = path.replace(/\/$/, ''); path = path.split('/'); return path; } function parseUrl(url) { var filePath = '', fileName = url, fileExtension = '', slash, dot, ext; url = url.replace(/\/{2,}/g, '/'); url = url.replace(/^\//, ''); url = url.replace(/\/$/, ''); // check if the url has a path and/or an extension slash = url.lastIndexOf('/'); if (slash !== -1) { fileName = url.substring(slash + 1); filePath = url.substring(0, slash); } dot = url.lastIndexOf('.'); if (dot !== -1) { ext = url.substring(dot + 1); if (ext.length >= 3 && ext.length <= 4) { fileExtension = ext; fileName = url.substring(slash + 1, dot); } } return { path: filePath, name: fileName, ext: fileExtension }; } // generic load method that calls the load() method of the item to be loaded // callback2 is called every time an item is loaded, callback1 is called after all items have been loaded function loadLoop(i, numItems, items, callback1, callback2) { if (numItems === 0) { if (callback1) { callback1(); } return; } var item = items[i]; item.load(function () { //console.log(item.name, 'loaded', i, numItems); if (callback2) { callback2(arguments); } i++; if (i < numItems) { loadLoop(i, numItems, items, callback1, callback2); } else { if (callback1) { callback1(); } } }); } function getArguments(args) { var result = [], loop, arg; args = slice.call(args); loop = function (data, i, maxi) { for (i = 0; i < maxi; i++) { arg = data[i]; if (typeString(arg) === 'array') { loop(arg, 0, arg.length); } else { result.push(arg); } } }; loop(args, 0, args.length); return result; } function getEqualPowerCurve(numSteps, type, maxValue) { var i, value, percent, values = new Float32Array(numSteps); for (i = 0; i < numSteps; i++) { percent = i / numSteps; if (type === 'fadeIn') { value = Math.cos((1.0 - percent) * 0.5 * Math.PI) * maxValue; } else if (type === 'fadeOut') { value = Math.cos(percent * 0.5 * Math.PI) * maxValue; } values[i] = value; if (i === numSteps - 1) { values[i] = type === 'fadeIn' ? 1 : 0; } } return values; } function remap(value, oldMin, oldMax, newMin, newMax) { var oldRange = oldMax - oldMin, newRange = newMax - newMin, result; result = (((value - oldMin) * newRange) / oldRange) + newMin; return result; } // filters assets with classname "name" from object "obj" and stores them in array "result" function filterItemsByClassName(obj, name, result) { var i, item, type; for (i in obj) { if (obj.hasOwnProperty(i)) { item = obj[i]; if (item.className === name) { result.push(item); } else { type = typeString(item); if (type === 'object') { loop(item, name, result); } } } } } function createSlider(config) { var slider = config.slider, message = config.message, label = config.label, sliderWrapper; //mouseDownCalls = [], //mouseMoveCalls = [], //mouseUpCalls = []; if (config.label === undefined) { label = slider.parentNode.firstChild; } if (config.initialSliderValue !== undefined) { slider.value = config.initialSliderValue; } if (config.initialLabelValue !== undefined) { label.innerHTML = message.replace('{value}', config.initialLabelValue); } if (config.min !== undefined) { slider.min = config.min; } if (config.max !== undefined) { slider.max = config.max; } if (config.step !== undefined) { slider.step = config.step; } function onMouseDown(e) { var value = slider.valueAsNumber; if (config.onMouseDown) { config.onMouseDown(value, e); } if (sliderWrapper.onMouseDown) { sliderWrapper.onMouseDown(value, e); } } function onMouseUp(e) { var value = slider.valueAsNumber; if (config.onMouseUp) { config.onMouseUp(value, e); } if (sliderWrapper.onMouseUp) { sliderWrapper.onMouseUp(value, e); } } function onMouseMove(e) { var value = slider.valueAsNumber; if (config.onMouseMove) { config.onMouseMove(value, e); } if (sliderWrapper.onMouseMove) { sliderWrapper.onMouseMove(value, e); } } function onChange(e) { var value = slider.valueAsNumber; if (config.onChange) { config.onChange(value, e); } if (sliderWrapper.onChange) { sliderWrapper.onChange(value, e); } } slider.addEventListener('mousedown', function (e) { setTimeout(onMouseDown, 0, e); slider.addEventListener('mousemove', onMouseMove, false); }, false); slider.addEventListener('mouseup', function (e) { setTimeout(onMouseUp, 0, e); slider.removeEventListener('mousemove', onMouseMove, false); }, false); slider.addEventListener('change', function (e) { //console.log('change'); onChange(e); }, false); sliderWrapper = { getValue: function () { if (config.getValue) { return config.getValue(slider.valueAsNumber); } else { return slider.valueAsNumber; } }, setValue: function (value) { if (config.setValue) { slider.value = config.setValue(value); } else { slider.value = value; } }, setLabel: function (value) { label.innerHTML = message.replace('{value}', value); }, elem: slider, element: slider, }; sliderWrapper.set = function (value) { setLabel(value); setValue(value); }; return sliderWrapper; } function createSlider2(config) { var slider = config.slider, message = config.message, label = slider.parentNode.firstChild; if (config.initialValueSlider) { slider.value = config.initialValueSlider; label.innerHTML = message.replace('{value}', calculate()); } if (config.initialValueLabel) { label.innerHTML = message.replace('{value}', config.initialValueLabel); slider.value = calculateFromLabel(config.initialValueLabel); } function onMouseMove() { var value = calculate(); if (config.onMouseMove !== undefined) { config.onMouseMove(slider.valueAsNumber, value); } label.innerHTML = message.replace('{value}', value); } function onMouseUp() { var value = calculate(); if (config.onMouseUp !== undefined) { config.onMouseUp(slider.valueAsNumber, value); } label.innerHTML = message.replace('{value}', value); } function onMouseDown() { var value = calculate(); if (config.onMouseDown !== undefined) { config.onMouseDown(slider.valueAsNumber, value); } label.innerHTML = message.replace('{value}', value); } function calculate() { var value = slider.valueAsNumber; if (config.calculate !== undefined) { value = config.calculate(value); } return value; } function calculateFromLabel(value) { if (config.calculateFromLabel !== undefined) { value = config.calculateFromLabel(value); } return value; } slider.addEventListener('mousedown', function () { setTimeout(onMouseDown, 0); slider.addEventListener('mousemove', onMouseMove, false); }, false); slider.addEventListener('mouseup', function () { setTimeout(onMouseUp, 0); slider.removeEventListener('mousemove', onMouseMove, false); }, false); return { updateSlider: function (value) { slider.value = value; label.innerHTML = message.replace('{value}', calculate(value)); }, updateLabel: function (value) { label.innerHTML = message.replace('{value}', value); slider.value = calculateFromLabel(value); }, getValue1: function () { return slider.valueAsNumber; }, getValue2: function () { return calculate(slider.valueAsNumber); } }; } function getRandom(min, max, round) { var r = mRandom() * (max - min) + min; if (round === true) { return mRound(r); } else { return r; } } function getRandomNotes(config) { var i, ticks = 0, events = [], midiEvent, velocity, numNotes, noteNumber, noteLength, minVelocity, maxVelocity, minNoteNumber, maxNoteNumber, ppq; //console.log(config); config = config || {}; ppq = config.ppq || sequencer.defaultPPQ; numNotes = config.numNotes || 20; noteLength = config.noteLength || ppq / 2; // ticks minVelocity = config.minVelocity || 30; maxVelocity = config.maxVelocity || 127; minNoteNumber = config.minNoteNumber || 60; maxNoteNumber = config.maxNoteNumber || 127; if (noteLength > ppq) { noteLength = ppq; } //console.log(ppq, numNotes, noteLength, minVelocity, maxVelocity, minNoteNumber, maxNoteNumber); for (i = 0; i < numNotes; i++) { noteNumber = getRandom(minNoteNumber, maxNoteNumber, true); velocity = getRandom(minVelocity, maxVelocity, true); //console.log(ticks, noteNumber, velocity); midiEvent = sequencer.createMidiEvent(ticks, sequencer.NOTE_ON, noteNumber, velocity); events.push(midiEvent); ticks += noteLength; midiEvent = sequencer.createMidiEvent(ticks, sequencer.NOTE_OFF, noteNumber, 0); events.push(midiEvent); ticks += ppq - noteLength; } return events; } function convertPPQ() {//oldPPQ, newPPQ, data, ..., ... var args = slice.call(arguments), oldPPQ = args[0], newPPQ = args[1], ratio = newPPQ / oldPPQ, i, event; if (isNaN(oldPPQ) || isNaN(newPPQ)) { if (sequencer.debug === 4) { console.error('PPQ values must be numbers'); } return; } function loop(data, i, maxi) { var arg, type, track, j, t; for (j = i; j < maxi; j++) { arg = data[j]; type = typeString(arg); //console.log(type, arg.className); if (type === 'array') { convert(arg); } else if (type === 'object') { if (arg.className === 'Part' || arg.className === 'Track' || arg.className === 'Song') { convert(arg.events); } else if (arg.className === 'MidiFile') { //console.log(arg.numTracks, arg.tracks[0].events); for (t = arg.numTracks - 1; t >= 0; t--) { track = arg.tracks[t]; //console.log(track.needsUpdate); if (track.needsUpdate === true) { track.update(); if (track.events) { convert(track.events); } } } } } } } loop(args, 2, args.length); function convert(events) { for (i = events.length - 1; i >= 0; i--) { event = events[i]; event.ticks = ratio * event.ticks; if (event.state !== 'new') { event.state = 'changed'; } } } } function getNoteLengthName(song, value) { for (var divider in noteLengthNames) { if (noteLengthNames.hasOwnProperty(divider)) { //console.log(value, song.ppq/divider); if (value === song.ppq / divider) { return noteLengthNames[divider]; } } } return false; } function getMicrosecondsFromBPM(bpm) { return round(60000000 / bpm); } function insertLink(s) { // @TODO: fix this -> should be md syntax var href, i = s.indexOf('http://'); if (i !== -1) { href = s.substring(i); i = href.indexOf(' '); if (i !== -1) { href = href.substring(0, i); } } return '<a href="' + href + '"></a>'; } function getWaveformData(buffer, config, callback) { var i, maxi, canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'), pcmRight = buffer.getChannelData(0), pcmLeft = buffer.getChannelData(0), numSamples = pcmRight.length, width, // max width of a canvas on chrome/chromium is 32000 height = config.height || 100, color = config.color || '#71DE71', bgcolor = config.bgcolor || '#000', density, scale = height / 2, sampleStep = config.sampleStep || 50, height, lastWidth, numImages, currentImage, xPos = 0, offset = 0, urls = [], imgElement, imgElements = []; //console.log(pcmRight.length, pcmLeft.length, config.samples.length); if (config.width !== undefined) { width = config.width; density = width / numSamples; } else { density = config.density || 1; width = 1000; lastWidth = (numSamples * config.density) % width; numImages = Math.ceil((numSamples * config.density) / width); currentImage = 0; } canvas.width = width; canvas.height = height; ctx.fillStyle = bgcolor; ctx.fillRect(0, 0, width, height); ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = 1; ctx.moveTo(0, scale); for (i = 0; i < numSamples; i += sampleStep) { xPos = (i - offset) * density; if (xPos >= width) { //console.log(width, height) //ctx.closePath(); ctx.stroke(); urls.push(canvas.toDataURL('image/png')); currentImage++; if (currentImage === numImages - 1) { canvas.width = lastWidth; } else { canvas.width = width; } ctx.beginPath(); ctx.strokeStyle = color; offset = i; xPos = 0; ctx.moveTo(xPos, scale - (pcmRight[i] * scale)); } else { ctx.lineTo(xPos, scale - (pcmRight[i] * scale)); //console.log(scale - (pcmRight[i] * scale)); } } if (xPos < width) { //ctx.closePath(); ctx.stroke(); urls.push(canvas.toDataURL('image/png')); } callback(urls); /* // create html image elements from the data-urls for(i = 0, maxi = urls.length; i < maxi; i++){ imgElement = document.createElement('img'); imgElement.src = urls[i]; imgElement.origWidth = imgElement.width; imgElement.height = 100; imgElements.push(imgElement); } callback({ dataURIs: urls, imgElements: imgElements }); */ } function encode64(buffer) { var binary = '', bytes = new Uint8Array(buffer), len = bytes.byteLength; for (var i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); } /*\ |*| |*| Base64 / binary data / UTF-8 strings utilities |*| |*| https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding |*| \*/ /* Array of bytes to base64 string decoding */ function b64ToUint6(nChr) { return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0; } function base64DecToArr(sBase64, nBlocksSize) { var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length, nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen); for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { nMod4 = nInIdx & 3; nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; if (nMod4 === 3 || nInLen - nInIdx === 1) { for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++ , nOutIdx++) { taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; } nUint24 = 0; } } return taBytes; } /* Base64 string to array encoding */ function uint6ToB64(nUint6) { return nUint6 < 26 ? nUint6 + 65 : nUint6 < 52 ? nUint6 + 71 : nUint6 < 62 ? nUint6 - 4 : nUint6 === 62 ? 43 : nUint6 === 63 ? 47 : 65; } function base64EncArr(aBytes) { var nMod3 = 2, sB64Enc = ""; for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { nMod3 = nIdx % 3; if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; } nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24); if (nMod3 === 2 || aBytes.length - nIdx === 1) { sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63)); nUint24 = 0; } } return sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + (nMod3 === 2 ? '' : nMod3 === 1 ? '=' : '=='); } /* UTF-8 array to DOMString and vice versa */ function UTF8ArrToStr(aBytes) { var sView = ""; for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) { nPart = aBytes[nIdx]; sView += String.fromCharCode( nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */ /* (nPart - 252 << 30) may be not so safe in ECMAScript! So...: */ (nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 : nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */ (nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 : nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */ (nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 : nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */ (nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128 : nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */ (nPart - 192 << 6) + aBytes[++nIdx] - 128 : /* nPart < 127 ? */ /* one byte */ nPart ); } return sView; } function strToUTF8Arr(sDOMStr) { var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0; /* mapping... */ for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) { nChr = sDOMStr.charCodeAt(nMapIdx); nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6; } aBytes = new Uint8Array(nArrLen); /* transcription... */ for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) { nChr = sDOMStr.charCodeAt(nChrIdx); if (nChr < 128) { /* one byte */ aBytes[nIdx++] = nChr; } else if (nChr < 0x800) { /* two bytes */ aBytes[nIdx++] = 192 + (nChr >>> 6); aBytes[nIdx++] = 128 + (nChr & 63); } else if (nChr < 0x10000) { /* three bytes */ aBytes[nIdx++] = 224 + (nChr >>> 12); aBytes[nIdx++] = 128 + (nChr >>> 6 & 63); aBytes[nIdx++] = 128 + (nChr & 63);