Compare commits
2 commits
fad1bbb875
...
667447b9a2
Author | SHA1 | Date | |
---|---|---|---|
667447b9a2 | |||
6ed4bd1227 |
4 changed files with 228 additions and 58 deletions
|
@ -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];
|
||||
|
@ -94,21 +138,7 @@ const Audio = function(tp, record) {
|
|||
return true;
|
||||
}
|
||||
|
||||
const addAudioOptions = (layer, propTitle) => {
|
||||
if (!started) {
|
||||
// audioOptions need a started init
|
||||
init();
|
||||
}
|
||||
const panelPropTitle = tp.getPanelPropTitle(propTitle);
|
||||
if (panelPropTitle === null) {
|
||||
console.log('Audio::addAudioOptions::error',`cannot find panelPropTitle "${propTitle}"`);
|
||||
return;
|
||||
}
|
||||
if (tp.getPanel().querySelector(toCssClass(`audioOptions${propTitle}`, '.')) !== null) {
|
||||
//console.log('Audio::addAudioOptions::error',`audioOptions already exist for "${propTitle}"`);
|
||||
return;
|
||||
}
|
||||
const container = panelPropTitle.parentNode.parentNode;
|
||||
const createAudioOptions = (layer, propTitle, container) => {
|
||||
const mappingOptions = mapping[layer.id()][propTitle];
|
||||
const panel = tp.getPanel();
|
||||
const audioOptions = document.createElement('div');
|
||||
|
@ -117,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 = () => {
|
||||
|
@ -171,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;
|
||||
|
@ -233,16 +284,42 @@ const Audio = function(tp, record) {
|
|||
freq_down = mapValue(e.clientX, bb.x, bb.x + bb.width, 0, config.audio.fftBandsUsed, true);
|
||||
});
|
||||
|
||||
//removeAudioOptions();
|
||||
container.after(audioOptions);
|
||||
|
||||
const audioButton = container.querySelector('.audioButton');
|
||||
audioButton.classList.add('active');
|
||||
|
||||
canvass.push(fft_imgDom);
|
||||
canvasCtxs.push(fft_imgDom.getContext("2d"));
|
||||
|
||||
updateMappingOptions();
|
||||
return audioOptions;
|
||||
};
|
||||
|
||||
const addAudioOptions = (layer, propTitle) => {
|
||||
if (!started) {
|
||||
// audioOptions need a started init
|
||||
init();
|
||||
}
|
||||
const panelPropTitle = tp.getPanelPropTitle(propTitle);
|
||||
if (panelPropTitle === null) {
|
||||
console.log('Audio::addAudioOptions::error',`cannot find panelPropTitle "${propTitle}"`);
|
||||
return;
|
||||
}
|
||||
if (tp.getPanel().querySelector(toCssClass(`audioOptions${propTitle}`, '.')) !== null) {
|
||||
//console.log('Audio::addAudioOptions::error',`audioOptions already exist for "${propTitle}"`);
|
||||
return;
|
||||
}
|
||||
const container = panelPropTitle.parentNode.parentNode;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const audioButton = container.querySelector('.audioButton');
|
||||
audioButton.classList.add('active');
|
||||
};
|
||||
|
||||
const removeAudioOptions = (layer = false, propTitle = false) => {
|
||||
|
@ -261,9 +338,9 @@ const Audio = function(tp, record) {
|
|||
// only selected layers have options
|
||||
// otherwise the ui is not there
|
||||
if (layer.isSelected()) {
|
||||
const audioOptions = panel.querySelector(toCssClass(`audioOptions${propTitle}`,'.'));
|
||||
if (audioOptions !== null) {
|
||||
audioOptions.remove();
|
||||
const audioOptions = panel.querySelectorAll(toCssClass(`audioOptions${propTitle}`,'.'));
|
||||
if (audioOptions.length > 0) {
|
||||
audioOptions.forEach((e) => { e.remove(); });
|
||||
}
|
||||
const audioButton = panel.querySelector(toCssClass(`audioButton${propTitle}`,'.'));
|
||||
if (audioButton !== null) {
|
||||
|
@ -400,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),
|
||||
|
@ -479,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;
|
||||
|
||||
|
@ -632,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) {
|
||||
|
@ -651,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -672,7 +756,7 @@ const Audio = function(tp, record) {
|
|||
}
|
||||
}
|
||||
|
||||
function voiceChange() {
|
||||
const voiceChange = () => {
|
||||
distortion.oversample = "4x";
|
||||
biquadFilter.gain.setTargetAtTime(0, audioCtx.currentTime, 0);
|
||||
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue