diff --git a/bin/web/js/audio.js b/bin/web/js/audio.js index 4f4250d..cf102e4 100644 --- a/bin/web/js/audio.js +++ b/bin/web/js/audio.js @@ -56,7 +56,42 @@ const Audio = function(tp, record) { return true; }; - const addAudioMapping = (layer, propTitle, options = new AudioMappingOptions()) => { + const getAudioMappingOptions = (layer, propTitle) => { + if (propTitle === 'color') { + if (config.audio.colorSeparateRGBA) { + const r = new AudioMappingOptions(); + const g = new AudioMappingOptions(); + const b = new AudioMappingOptions(); + const a = new AudioMappingOptions(); + return [{r}, {g}, {b}, {a}]; + } else { + const rgba = new AudioMappingOptions(); + rgba.min_out = {r: 0, b: 0, g: 0, a: 0}; + rgba.max_out = {r: 1, b: 1, g: 1, a: 1}; + return rgba; + } + } else { + const o = new AudioMappingOptions(); + // TODO: get min_out, max_out from layer.props + // check for typeof layer.props[propTitle.split('.')[0]] blabla + return o; + } + }; + + // potentially recursive + const addAudioMapping = (layer, propTitle, options = false) => { + if (!options) { + options = getAudioMappingOptions(layer, propTitle); + if (Array.isArray(options)) { + let isGood = true; + options.forEach((o) => { + const subPropKey = Object.keys(o)[0]; + const subPropTitle = `${propTitle}.${subPropKey}`; + isGood = addAudioMapping(layer, subPropTitle, o[subPropKey]) ? isGood : false; + }); + return isGood; + } + } if (!mapping.hasOwnProperty(layer.id())) { mapping[layer.id()] = {}; } @@ -85,6 +120,15 @@ const Audio = function(tp, record) { } if (!mapping[layer.id()].hasOwnProperty(propTitle)) { // no propTitle + // perhaps color? + if (config.audio.separateRGBA && propTitle === 'color') { + let isGood = true; + isGood = removeAudioMapping(layer, 'color.r'); + isGood = removeAudioMapping(layer, 'color.g'); + isGood = removeAudioMapping(layer, 'color.b'); + isGood = removeAudioMapping(layer, 'color.a'); + return isGood; + } return false; } delete mapping[layer.id()][propTitle]; @@ -103,7 +147,28 @@ const Audio = function(tp, record) { audioOptions.classList.add(toCssClass(`audioOptions${propTitle}`)); audioOptions.style.position = 'relative'; audioOptions.style.width = '100%'; - audioOptions.style.background = 'rgba(0,255,255,0.2)'; + if (propTitle.split('.')[0] === 'color' && propTitle.split('.').length > 1) { + switch(propTitle.split('.')[1]) { + case 'r': { + audioOptions.style.background = 'rgba(255,0,0,0.2)'; + break; + } + case 'g': { + audioOptions.style.background = 'rgba(0,255,0,0.2)'; + break; + } + case 'b': { + audioOptions.style.background = 'rgba(0,0,255,0.2)'; + break; + } + case 'a': { + audioOptions.style.background = 'rgba(255,255,255,0.2)'; + break; + } + } + } else { + audioOptions.style.background = 'rgba(0,255,255,0.2)'; + } audioOptions.style.order = parseInt(container.style.order) + 1; const updateMappingOptions = () => { @@ -157,7 +222,7 @@ const Audio = function(tp, record) { sync_titleDom.innerHTML = 'sync with:'; sync_Dom.append(sync_titleDom); - audio_sync_options.forEach((o, oi) => { + audio_sync_options.forEach((o) => { const sync_inputDom_label = document.createElement('label'); sync_inputDom_label.for = `audio_sync${o}`; sync_inputDom_label.innerHTML = o; @@ -243,11 +308,12 @@ const Audio = function(tp, record) { } const container = panelPropTitle.parentNode.parentNode; - if (propTitle === 'color') { - createAudioOptions(layer, `${propTitle}.r`, container).classList.add(toCssClass(`audioOptions${propTitle}`)); - createAudioOptions(layer, `${propTitle}.g`, container).classList.add(toCssClass(`audioOptions${propTitle}`)); - createAudioOptions(layer, `${propTitle}.b`, container).classList.add(toCssClass(`audioOptions${propTitle}`)); + if (propTitle === 'color' && config.audio.colorSeparateRGBA) { + // NOTE: attach reversed, because container.after(audioOptions) createAudioOptions(layer, `${propTitle}.a`, container).classList.add(toCssClass(`audioOptions${propTitle}`)); + createAudioOptions(layer, `${propTitle}.b`, container).classList.add(toCssClass(`audioOptions${propTitle}`)); + createAudioOptions(layer, `${propTitle}.g`, container).classList.add(toCssClass(`audioOptions${propTitle}`)); + createAudioOptions(layer, `${propTitle}.r`, container).classList.add(toCssClass(`audioOptions${propTitle}`)); } else { createAudioOptions(layer, propTitle, container); } @@ -411,7 +477,7 @@ const Audio = function(tp, record) { // Distortion curve for the waveshaper, thanks to Kevin Ennis // http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion - function makeDistortionCurve(amount) { + const makeDistortionCurve = (amount) => { let k = typeof amount === "number" ? amount : 50, n_samples = 44100, curve = new Float32Array(n_samples), @@ -490,7 +556,7 @@ const Audio = function(tp, record) { console.log("getUserMedia not supported on your browser!"); } - function visualize() { + const visualize = () => { const WIDTH = canvas.width; const HEIGHT = canvas.height; @@ -643,18 +709,21 @@ const Audio = function(tp, record) { }; }); Object.keys(values).forEach((layerID) => { + window.debugPreValues = clone(values[layerID]); deFlattenObject(values[layerID]); + window.debugValues = clone(values[layerID]); record.liveUpdater.immediateUpdate(getLayer(layerID), values[layerID]); }); } } else { + const position = tp.sheet.sequence.position; propsToSet.forEach((p) => { const title = tp - .getPanelPropTitle(p.title) - .parentNode.parentNode; + .getPanelPropTitle(p.title); if (title !== null) { const inputElement = title + .parentNode.parentNode .querySelector('input.recording'); if (inputElement !== null) { @@ -662,6 +731,10 @@ const Audio = function(tp, record) { inputElement.dispatchEvent(new Event('change')); } } + record.addValue(p.id, p.title, p.value, position); + if (!config.audio.colorSeparateRGBA || p.title === 'color.a') { + record.liveUpdate(p.layer, position); + } }); } } @@ -683,7 +756,7 @@ const Audio = function(tp, record) { } } - function voiceChange() { + const voiceChange = () => { distortion.oversample = "4x"; biquadFilter.gain.setTargetAtTime(0, audioCtx.currentTime, 0); diff --git a/bin/web/js/config.js b/bin/web/js/config.js index 85834e4..6694181 100644 --- a/bin/web/js/config.js +++ b/bin/web/js/config.js @@ -88,6 +88,7 @@ const config = { fftBandsAnalysed: 256 * 8, fftBandsUsed: 256 * 8 / 2, fftHeight: 256 / 2, + colorSeparateRGBA: true, }, record: { ignoreProps: ['transformOrigin', 'fontFamily', 'text', 'mirror_x', 'mirror_y', 'mirror_xy'], diff --git a/bin/web/js/record.js b/bin/web/js/record.js index dc7511c..5ba73bc 100644 --- a/bin/web/js/record.js +++ b/bin/web/js/record.js @@ -49,7 +49,7 @@ const LiveBuffer = function() { subValueBuffer.delete(value_time_s); break; } - }; + } } }); } @@ -73,7 +73,7 @@ const LiveBuffer = function() { let mergedValues = {}; let didMergeValues = {}; valueBuffer[id].forEach((value, value_time_s) => { - if (value_time_s < time_s) { + if (value_time_s <= time_s) { mergedValues = {...mergedValues, ...value}; } else { if (Object.keys(didMergeValues).length === 0) { @@ -116,7 +116,20 @@ const LiveUpdater = function(tp, buffy) { } }; this.immediateUpdate = (layer, values) => { - const v = {...layer.theatreObject.value, ...values}; + const cv = clone(values); + if (cv.hasOwnProperty('color.r')) { + cv['color'] = { + r: cv['color.r'], + g: cv['color.g'], + b: cv['color.b'], + a: cv['color.a'], + }; + delete cv['color.r']; + delete cv['color.g']; + delete cv['color.b']; + delete cv['color.a']; + } + const v = {...layer.theatreObject.value, ...cv}; const p = layer.values2cppProps(v); if (p !== false) { const id = layer.id(); @@ -152,8 +165,13 @@ const Record = function(tp) { buffy.register(layerID); // handle UI only if layer is selected if (getLayer(layerID).isSelected()) { + let cPropTitle = clone(propTitle); + // if colors are separate, there is still just one button + if (cPropTitle.indexOf('color.') === 0) { + cPropTitle = 'color'; + } const button = tp - .getPanelPropTitle(propTitle) + .getPanelPropTitle(cPropTitle) .parentNode.parentNode .querySelector('.recordButton'); if (button !== null) { @@ -174,8 +192,13 @@ const Record = function(tp) { } // handle UI only if layer is selected if (getLayer(layerID).isSelected()) { + let cPropTitle = clone(propTitle); + // if colors are separate, there is still just one button + if (cPropTitle.indexOf('color.') === 0) { + cPropTitle = 'color'; + } const button = tp - .getPanelPropTitle(propTitle) + .getPanelPropTitle(cPropTitle) .parentNode.parentNode .querySelector('.recordButton'); if (button !== null) { @@ -310,8 +333,44 @@ const Record = function(tp) { }); }; + let lastPositions = {}; + const addValue = (layerID, + propTitle, + value, + position = tp.sheet.sequence.position, + lastPosition = buffy.NO_TIME) => { + hot[layerID][propTitle].recording.push({ + position, + value, + }); + const recording = { + [propTitle]: value, + }; + if (!lastPositions.hasOwnProperty(layerID)) { + lastPositions[layerID] = {}; + } + if (lastPosition === buffy.NO_TIME) { + if (!lastPositions[layerID].hasOwnProperty(propTitle)) { + lastPositions[layerID][propTitle] = position; + } + } else { + lastPositions[layerID][propTitle] = lastPosition; + } + buffy.addValues(layerID, recording, position, lastPositions[layerID][propTitle]); + }; + const getValue = (layerID, position) => { + const merged = clone(buffy.getValues(layerID, position)); + deFlattenObject(merged); + return merged; + }; + const liveUpdate = (layer, position = tp.sheet.sequence.position) => { + const merged = getValue(layer.id(), position); + liveUpdater.immediateUpdate(layer, merged); + }; + const startRecording = () => { console.log('Record::startRecording'); + lastPositions = {}; tp.sheet.sequence.pause(); const layerKeys = Object.keys(hot); layerKeys.forEach((layerID) => { @@ -324,22 +383,14 @@ const Record = function(tp) { const input_clone = cloneInput(layerID, propTitle); let lastPosition = buffy.NO_TIME; if (input_clone !== null) { - input_clone.addEventListener('change', (e) => { - const position = tp.sheet.sequence.position; - const value = parseFloat(input_clone.value); - hot[layerID][propTitle].recording.push({ - position, - value, - }); - const recording = { - [propTitle]: value, - }; - buffy.addValues(layerID, recording, position, lastPosition); - const merged = clone(buffy.getValues(layerID, position)); - deFlattenObject(merged); - liveUpdater.immediateUpdate(layer, merged); - lastPosition = position; - }); + //input_clone.addEventListener('change', () => { + //const position = tp.sheet.sequence.position; + //const value = parseFloat(input_clone.value); + //addValue(layerID, propTitle, value, position, lastPosition); + //const merged = getValue(layerID, position); + //liveUpdater.immediateUpdate(layer, merged); + //lastPosition = position; + //}); } else { console.log('Record::startRecording', `whoops input_clone for ${propTitle} is null`); } @@ -373,12 +424,39 @@ const Record = function(tp) { // NOTE: layerID is not actually used atm // and should be the layer anyways uncloneInput(layerID, propTitle); - keyframes.push({ - path: propTitle.split('.'), - keyframes: hot[layerID][propTitle].recording, - }); + // special treatment if we have sperate RGBA for color + if (propTitle.indexOf('color.') === 0) { + if (propTitle === 'color.r') { + const recording = []; + hot[layerID]['color.r'].recording.forEach((rr, ri) => { + if (ri < hot[layerID]['color.r'].recording.length && + ri < hot[layerID]['color.g'].recording.length && + ri < hot[layerID]['color.b'].recording.length && + ri < hot[layerID]['color.a'].recording.length) { + const r = clone(rr); + r.value = { + r: hot[layerID]['color.r'].recording[ri].value, + g: hot[layerID]['color.g'].recording[ri].value, + b: hot[layerID]['color.b'].recording[ri].value, + a: hot[layerID]['color.a'].recording[ri].value, + }; + recording.push(r); + } + }); + keyframes.push({ + path: ['color'], + keyframes: recording, + }); + } + } else { + keyframes.push({ + path: propTitle.split('.'), + keyframes: hot[layerID][propTitle].recording, + }); + } }); //setTimeout(() => { + const kf = clone(keyframes); promises.push(() => { return new Promise((subResolve) => { tp.setKeyframes(layer, keyframes).then(() => { @@ -404,6 +482,9 @@ const Record = function(tp) { }; // public + this.addValue = addValue; + this.getValue = getValue; + this.liveUpdate = liveUpdate; this.liveUpdater = liveUpdater; this.addRecordButton = addRecordButton; this.addHot = addHot; diff --git a/bin/web/js/utils.js b/bin/web/js/utils.js index f7d04a6..958ce23 100644 --- a/bin/web/js/utils.js +++ b/bin/web/js/utils.js @@ -347,7 +347,7 @@ function verifyVariableTimeProject(vt_project) { function clone(a) { return JSON.parse(JSON.stringify(a)); -}; +} function getParents(elem, until = null) { const parents = []; @@ -461,7 +461,11 @@ const deFlattenObject = (o, ignoreKeys = [], pathSymbol = '.') => { for (let i = ks.length - 1; i > 0; i--) { sos = {[ks[i]]: sos}; } - o[ks[0]] = sos; + if (typeof o[ks[0]] === 'object') { + o[ks[0]] = {...o[ks[0]], ...sos}; + } else { + o[ks[0]] = sos; + } delete o[k]; } }