record and set keyframes, introduce RecordingBuffer, avoid toDataUrl() for fft visualisation
This commit is contained in:
parent
64b3d26384
commit
7f6ab572c7
6 changed files with 431 additions and 95 deletions
|
@ -13,6 +13,8 @@ const Audio = function(tp, record) {
|
||||||
let started = false;
|
let started = false;
|
||||||
|
|
||||||
const mapping = {};
|
const mapping = {};
|
||||||
|
const canvass = [];
|
||||||
|
const canvasCtxs = [];
|
||||||
|
|
||||||
const addAudioOptions = (layer, propTitle) => {
|
const addAudioOptions = (layer, propTitle) => {
|
||||||
const panelPropTitle = tp.getPanelPropTitle(propTitle);
|
const panelPropTitle = tp.getPanelPropTitle(propTitle);
|
||||||
|
@ -20,6 +22,10 @@ const Audio = function(tp, record) {
|
||||||
console.log('Audio::addAudioOptions::error',`cannot find panelPropTitle "${propTitle}"`);
|
console.log('Audio::addAudioOptions::error',`cannot find panelPropTitle "${propTitle}"`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (tp.getPanel().querySelector(`.audioOptions${propTitle}`) !== null) {
|
||||||
|
console.log('Audio::addAudioOptions::error',`audioOptions already exist for "${propTitle}"`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const container = tp.getPanelPropContainer(panelPropTitle);
|
const container = tp.getPanelPropContainer(panelPropTitle);
|
||||||
const mappingOptions = mapping[layer.id()][propTitle];
|
const mappingOptions = mapping[layer.id()][propTitle];
|
||||||
const panel = tp.getPanel();
|
const panel = tp.getPanel();
|
||||||
|
@ -30,10 +36,10 @@ const Audio = function(tp, record) {
|
||||||
audioOptions.style.position = 'relative';
|
audioOptions.style.position = 'relative';
|
||||||
audioOptions.style.width = '100%';
|
audioOptions.style.width = '100%';
|
||||||
audioOptions.style.background = 'rgba(0,255,255,0.2)';
|
audioOptions.style.background = 'rgba(0,255,255,0.2)';
|
||||||
audioOptions.style.order = window.getComputedStyle(container).order;
|
audioOptions.style.order = parseInt(container.style.order) + 1;
|
||||||
|
|
||||||
mappingOptions.freq_min = 0;
|
mappingOptions.freq_min = 0;
|
||||||
mappingOptions.freq_max = 256 * 8 / 2;
|
mappingOptions.freq_max = config.audio.fftBandsUsed;
|
||||||
|
|
||||||
const updateMappingOptions = () => {
|
const updateMappingOptions = () => {
|
||||||
mappingOptions.min_out = parseFloat(panel.querySelector(`#audio_min${propTitle}`).value);
|
mappingOptions.min_out = parseFloat(panel.querySelector(`#audio_min${propTitle}`).value);
|
||||||
|
@ -107,7 +113,7 @@ const Audio = function(tp, record) {
|
||||||
audioOptions.append(sync_Dom);
|
audioOptions.append(sync_Dom);
|
||||||
|
|
||||||
const fft_Dom = document.createElement('div');
|
const fft_Dom = document.createElement('div');
|
||||||
const fft_imgDom = document.createElement('img');
|
const fft_imgDom = document.createElement('canvas');
|
||||||
const fft_selectDom = document.createElement('div');
|
const fft_selectDom = document.createElement('div');
|
||||||
fft_Dom.style.position = 'relative';
|
fft_Dom.style.position = 'relative';
|
||||||
fft_Dom.style.top = '0px';
|
fft_Dom.style.top = '0px';
|
||||||
|
@ -117,6 +123,8 @@ const Audio = function(tp, record) {
|
||||||
fft_imgDom.style.userDrag = 'none';
|
fft_imgDom.style.userDrag = 'none';
|
||||||
fft_imgDom.style.userSelect = 'none';
|
fft_imgDom.style.userSelect = 'none';
|
||||||
fft_imgDom.style.pointerEvents = 'none';
|
fft_imgDom.style.pointerEvents = 'none';
|
||||||
|
fft_imgDom.setAttribute('width', config.audio.fftBandsUsed);
|
||||||
|
fft_imgDom.setAttribute('height', config.audio.fftHeight);
|
||||||
fft_selectDom.style.position = 'absolute';
|
fft_selectDom.style.position = 'absolute';
|
||||||
fft_selectDom.style.top = '0px';
|
fft_selectDom.style.top = '0px';
|
||||||
fft_selectDom.style.left = '0px';
|
fft_selectDom.style.left = '0px';
|
||||||
|
@ -138,18 +146,21 @@ const Audio = function(tp, record) {
|
||||||
setFrequency = true;
|
setFrequency = true;
|
||||||
const bb = fft_Dom.getBoundingClientRect();
|
const bb = fft_Dom.getBoundingClientRect();
|
||||||
const x = e.clientX - bb.x;
|
const x = e.clientX - bb.x;
|
||||||
freq_down = mapValue(e.clientX, bb.x, bb.x + bb.width, 0, 256 * 8 / 2, true);
|
freq_down = mapValue(e.clientX, bb.x, bb.x + bb.width, 0, config.audio.fftBandsUsed, true);
|
||||||
});
|
});
|
||||||
fft_Dom.addEventListener('mouseup', (e) => {
|
fft_Dom.addEventListener('mouseup', (e) => {
|
||||||
setFrequency = false;
|
setFrequency = false;
|
||||||
const bb = fft_Dom.getBoundingClientRect();
|
const bb = fft_Dom.getBoundingClientRect();
|
||||||
const x = e.clientX - bb.x;
|
const x = e.clientX - bb.x;
|
||||||
freq_down = mapValue(e.clientX, bb.x, bb.x + bb.width, 0, 256 * 8 / 2, true);
|
freq_down = mapValue(e.clientX, bb.x, bb.x + bb.width, 0, config.audio.fftBandsUsed, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
//removeAudioOptions();
|
//removeAudioOptions();
|
||||||
container.after(audioOptions);
|
container.after(audioOptions);
|
||||||
|
|
||||||
|
canvass.push(fft_imgDom);
|
||||||
|
canvasCtxs.push(fft_imgDom.getContext("2d"));
|
||||||
|
|
||||||
updateMappingOptions();
|
updateMappingOptions();
|
||||||
mappingOptions.value = mappingOptions.min_out;
|
mappingOptions.value = mappingOptions.min_out;
|
||||||
};
|
};
|
||||||
|
@ -346,7 +357,7 @@ const Audio = function(tp, record) {
|
||||||
const canvasCtx = canvas.getContext("2d");
|
const canvasCtx = canvas.getContext("2d");
|
||||||
|
|
||||||
const intendedWidth = audioDom.clientWidth;
|
const intendedWidth = audioDom.clientWidth;
|
||||||
canvas.setAttribute("width", 256 * 8 / 2);
|
canvas.setAttribute("width", config.audio.fftBandsUsed);
|
||||||
const visualSelect = audioDom.querySelector("#visual");
|
const visualSelect = audioDom.querySelector("#visual");
|
||||||
let drawVisual;
|
let drawVisual;
|
||||||
|
|
||||||
|
@ -428,13 +439,17 @@ const Audio = function(tp, record) {
|
||||||
|
|
||||||
draw();
|
draw();
|
||||||
} else if (visualSetting == "frequencybars") {
|
} else if (visualSetting == "frequencybars") {
|
||||||
analyser.fftSize = 256 * 8;
|
analyser.fftSize = config.audio.fftBandsAnalysed;
|
||||||
|
const w = config.audio.fftBandsUsed;
|
||||||
|
const h = config.audio.fftHeight;
|
||||||
const bufferLengthAlt = analyser.frequencyBinCount / 2;
|
const bufferLengthAlt = analyser.frequencyBinCount / 2;
|
||||||
|
|
||||||
// See comment above for Float32Array()
|
// See comment above for Float32Array()
|
||||||
const dataArrayAlt = new Uint8Array(bufferLengthAlt);
|
const dataArrayAlt = new Uint8Array(bufferLengthAlt);
|
||||||
|
|
||||||
canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
|
for (let i = 0; i < canvasCtxs.length; i++) {
|
||||||
|
canvasCtxs[i].clearRect(0, 0, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
let frameCount = 0;
|
let frameCount = 0;
|
||||||
const drawAlt = function() {
|
const drawAlt = function() {
|
||||||
|
@ -442,10 +457,12 @@ const Audio = function(tp, record) {
|
||||||
|
|
||||||
analyser.getByteFrequencyData(dataArrayAlt);
|
analyser.getByteFrequencyData(dataArrayAlt);
|
||||||
|
|
||||||
canvasCtx.fillStyle = "rgb(0, 0, 0)";
|
for (let i = 0; i < canvasCtxs.length; i++) {
|
||||||
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
|
canvasCtxs[i].fillStyle = "rgb(0, 0, 0)";
|
||||||
|
canvasCtxs[i].fillRect(0, 0, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
const barWidth = (WIDTH / bufferLengthAlt) * 2.5;
|
const barWidth = (w / bufferLengthAlt) * 2.5;
|
||||||
let barHeight;
|
let barHeight;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
|
|
||||||
|
@ -458,13 +475,15 @@ const Audio = function(tp, record) {
|
||||||
max_v = barHeight;
|
max_v = barHeight;
|
||||||
max_i = i;
|
max_i = i;
|
||||||
}
|
}
|
||||||
canvasCtx.fillStyle = "rgb(" + (barHeight + 100) + ",50,50)";
|
for (let i = 0; i < canvasCtxs.length; i++) {
|
||||||
canvasCtx.fillRect(
|
canvasCtxs[i].fillStyle = "rgb(" + (barHeight + 100) + ",50,50)";
|
||||||
x,
|
canvasCtxs[i].fillRect(
|
||||||
HEIGHT - barHeight / 2,
|
x,
|
||||||
barWidth,
|
h - barHeight / 2,
|
||||||
barHeight / 2
|
barWidth,
|
||||||
);
|
barHeight / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
x += barWidth + 1;
|
x += barWidth + 1;
|
||||||
}
|
}
|
||||||
|
@ -478,6 +497,8 @@ const Audio = function(tp, record) {
|
||||||
let a = mapValue(max_v, 0, 255, m.min_out, m.max_out, true);
|
let a = mapValue(max_v, 0, 255, m.min_out, m.max_out, true);
|
||||||
m.value = m.value * m.smoothing + (1.0 - m.smoothing) * a;
|
m.value = m.value * m.smoothing + (1.0 - m.smoothing) * a;
|
||||||
propsToSet.push({
|
propsToSet.push({
|
||||||
|
layer,
|
||||||
|
id: layer.id(),
|
||||||
title: propTitle,
|
title: propTitle,
|
||||||
prop: layer.theatreObject.props[propTitle],
|
prop: layer.theatreObject.props[propTitle],
|
||||||
value: m.value,
|
value: m.value,
|
||||||
|
@ -488,6 +509,8 @@ const Audio = function(tp, record) {
|
||||||
let a = mapValue(max_i, 0, bufferLengthAlt-1, m.min_out, m.max_out, true);
|
let a = mapValue(max_i, 0, bufferLengthAlt-1, m.min_out, m.max_out, true);
|
||||||
m.value = m.value * m.smoothing + (1.0 - m.smoothing) * a;
|
m.value = m.value * m.smoothing + (1.0 - m.smoothing) * a;
|
||||||
propsToSet.push({
|
propsToSet.push({
|
||||||
|
layer,
|
||||||
|
id: layer.id(),
|
||||||
title: propTitle,
|
title: propTitle,
|
||||||
prop: layer.theatreObject.props[propTitle],
|
prop: layer.theatreObject.props[propTitle],
|
||||||
value: m.value,
|
value: m.value,
|
||||||
|
@ -503,37 +526,57 @@ const Audio = function(tp, record) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (propsToSet.length > 0 && frameCount % 2 === 0) {
|
if (propsToSet.length > 0 && frameCount % 2 === 0) {
|
||||||
|
// this is when to monitor live
|
||||||
if (!record.isRecording()) {
|
if (!record.isRecording()) {
|
||||||
tp.studio.transaction(({
|
if (!tp.core.val(tp.sheet.sequence.pointer.playing)) {
|
||||||
set
|
if (typeof window.immediateUpdate !== 'function') {
|
||||||
}) => {
|
window.immediateUpdate = (layer, values) => {
|
||||||
|
const v = {
|
||||||
|
...layer.theatreObject.value,
|
||||||
|
...values
|
||||||
|
};
|
||||||
|
const p = layer.values2cppProps(v);
|
||||||
|
if (p !== false) {
|
||||||
|
const id = layer.id();
|
||||||
|
if (id !== 'artboard') {
|
||||||
|
Module.setProps(p, layer.id());
|
||||||
|
} else {
|
||||||
|
Module.setArtboardProps(p, layer.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
propsToSet.forEach((p) => {
|
propsToSet.forEach((p) => {
|
||||||
set(p.prop, p.value, true);
|
immediateUpdate(p.layer, {
|
||||||
|
[p.title]: p.value
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
} else {
|
} else {
|
||||||
propsToSet.forEach((p) => {
|
propsToSet.forEach((p) => {
|
||||||
// TODO: this does not have to be queried
|
const title = tp
|
||||||
// but we could store it in a map/set/dictionary/array/object
|
.getPanelPropContainer(p.title);
|
||||||
const inputElement = tp
|
|
||||||
.getPanelPropContainer(p.title)
|
|
||||||
.querySelector('input.recording');
|
|
||||||
|
|
||||||
if (inputElement !== null) {
|
if (title !== null) {
|
||||||
inputElement.value = p.value;
|
const inputElement = title
|
||||||
inputElement.dispatchEvent(new Event('change'));
|
.querySelector('input.recording');
|
||||||
|
|
||||||
|
if (inputElement !== null) {
|
||||||
|
inputElement.value = p.value;
|
||||||
|
inputElement.dispatchEvent(new Event('change'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const panel = tp.getPanel();
|
//const panel = tp.getPanel();
|
||||||
const fft_images = panel.querySelectorAll('.audio_fft');
|
//const fft_images = panel.querySelectorAll('.audio_fft');
|
||||||
if (fft_images !== null) {
|
//if (fft_images !== null) {
|
||||||
const src = canvas.toDataURL();
|
//const src = canvas.toDataURL();
|
||||||
fft_images.forEach((e) => {
|
//fft_images.forEach((e) => {
|
||||||
e.src = src;
|
//e.src = src;
|
||||||
});
|
//});
|
||||||
}
|
//}
|
||||||
frameCount++;
|
frameCount++;
|
||||||
};
|
};
|
||||||
drawAlt();
|
drawAlt();
|
||||||
|
|
|
@ -85,6 +85,9 @@ const config = {
|
||||||
audio: {
|
audio: {
|
||||||
ignoreProps: ['transformOrigin', 'fontFamily', 'text', 'mirror_x', 'mirror_y', 'mirror_xy', 'fontVariationAxes', 'color'],
|
ignoreProps: ['transformOrigin', 'fontFamily', 'text', 'mirror_x', 'mirror_y', 'mirror_xy', 'fontVariationAxes', 'color'],
|
||||||
defaultSmoothing: 0.7,
|
defaultSmoothing: 0.7,
|
||||||
|
fftBandsAnalysed: 256 * 8,
|
||||||
|
fftBandsUsed: 256 * 8 / 2,
|
||||||
|
fftHeight: 256 / 2,
|
||||||
},
|
},
|
||||||
record: {
|
record: {
|
||||||
ignoreProps: ['fontVariationAxes','letterDelays','color'],
|
ignoreProps: ['fontVariationAxes','letterDelays','color'],
|
||||||
|
|
|
@ -188,6 +188,7 @@ window.onload = () => {
|
||||||
alert('Sorry, Variable Time is a tool currently designed to be used on desktop!');
|
alert('Sorry, Variable Time is a tool currently designed to be used on desktop!');
|
||||||
}
|
}
|
||||||
window.addEventListener('panelEvent', (e) => {
|
window.addEventListener('panelEvent', (e) => {
|
||||||
|
console.log('debug panelEvent received', e);
|
||||||
clearTimeout(window.panelFinderTimeout);
|
clearTimeout(window.panelFinderTimeout);
|
||||||
let target = false;
|
let target = false;
|
||||||
if (e.detail.panelID === 'artboard') {
|
if (e.detail.panelID === 'artboard') {
|
||||||
|
@ -203,6 +204,7 @@ window.onload = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener('sequenceEvent', (e) => {
|
window.addEventListener('sequenceEvent', (e) => {
|
||||||
|
console.log('debug sequenceEvent received', e);
|
||||||
let target = false;
|
let target = false;
|
||||||
if (e.detail.panelID === 'artboard') {
|
if (e.detail.panelID === 'artboard') {
|
||||||
target = artboard;
|
target = artboard;
|
||||||
|
|
|
@ -1,7 +1,163 @@
|
||||||
|
import {
|
||||||
|
clone,
|
||||||
|
sequencialPromises,
|
||||||
|
} from './utils.js';
|
||||||
|
|
||||||
|
const LiveBuffer = function() {
|
||||||
|
// private
|
||||||
|
//
|
||||||
|
// constants
|
||||||
|
const NO_TIME = -1;
|
||||||
|
// variables
|
||||||
|
|
||||||
|
/// @brief valueBuffer stores all values.
|
||||||
|
/// it is an object with layerIDs/artboard as key
|
||||||
|
const valueBuffer = {};
|
||||||
|
|
||||||
|
// functions
|
||||||
|
const register = (id) => {
|
||||||
|
if (!valueBuffer.hasOwnProperty(id)) {
|
||||||
|
valueBuffer[id] = new Map();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const deregister = (id) => {
|
||||||
|
if (valueBuffer.hasOwnProperty(id)) {
|
||||||
|
delete valueBuffer[id];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/// @brief addValues
|
||||||
|
// values are expected to be
|
||||||
|
// {
|
||||||
|
// x: 42.0,
|
||||||
|
// fontSize_px: 24,
|
||||||
|
// whatever: "something",
|
||||||
|
// }
|
||||||
|
const addValues = (id, values, time_s, start_time_s) => {
|
||||||
|
const subValueBuffer = valueBuffer[id];
|
||||||
|
|
||||||
|
// delete between start_time_s and time_s
|
||||||
|
if (start_time_s !== NO_TIME) {
|
||||||
|
subValueBuffer.forEach((value, value_time_s) => {
|
||||||
|
if (value_time_s > start_time_s && value_time_s <= time_s) {
|
||||||
|
const keys = Object.keys(values);
|
||||||
|
for (let k = 0; k < keys.length; k++) {
|
||||||
|
delete subValueBuffer.get(value_time_s)[keys[k]];
|
||||||
|
if (Object.keys(subValueBuffer.get(value_time_s)).length === 0) {
|
||||||
|
subValueBuffer.delete(value_time_s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (subValueBuffer.has(time_s)) {
|
||||||
|
subValueBuffer.set(time_s, {...subValueBuffer.get(time_s), ...values});
|
||||||
|
} else {
|
||||||
|
subValueBuffer.set(time_s, clone(values));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// get values and merge with previous
|
||||||
|
// 0.42s: { x: 4 }
|
||||||
|
// 0.64s: { y: 7 }
|
||||||
|
// 0.82s: { y: 12, z: 24 }
|
||||||
|
//
|
||||||
|
// return for 0.82s = { x: 4, y: 12, x: 24 }
|
||||||
|
const getValues = (id, time_s) => {
|
||||||
|
if (valueBuffer[id].size === 0) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
valueBuffer[id] = new Map([...valueBuffer[id].entries()].sort());
|
||||||
|
let mergedValues = {};
|
||||||
|
let didMergeValues = {};
|
||||||
|
valueBuffer[id].forEach((value, value_time_s) => {
|
||||||
|
if (value_time_s < time_s) {
|
||||||
|
mergedValues = {...mergedValues, ...value};
|
||||||
|
} else {
|
||||||
|
if (Object.keys(didMergeValues).length === 0) {
|
||||||
|
didMergeValues = clone(mergedValues);
|
||||||
|
}
|
||||||
|
Object.keys(value).forEach((key) => {
|
||||||
|
if(!didMergeValues.hasOwnProperty(key)) {
|
||||||
|
mergedValues[key] = value[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return mergedValues;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// public
|
||||||
|
this.NO_TIME = NO_TIME;
|
||||||
|
this.addValues = addValues;
|
||||||
|
this.getValues = getValues;
|
||||||
|
this.register = register;
|
||||||
|
this.deregister = deregister;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LiveUpdater = function(tp, buffy) {
|
||||||
|
const toUpdate = [];
|
||||||
|
const update = () => {
|
||||||
|
const time_s = tp.sheet.sequence.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.add = (layer) => {
|
||||||
|
if (toUpdate.indexOf(layer) < 0) {
|
||||||
|
toUpdate.push(layer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.remove = (layer) => {
|
||||||
|
const index = toUpdate.indexOf(layer);
|
||||||
|
if (index >= 0) {
|
||||||
|
toUpdate.push(layer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.immediateUpdate = (layer, values) => {
|
||||||
|
const v = {...layer.theatreObject.value, ...values};
|
||||||
|
const p = layer.values2cppProps(v);
|
||||||
|
if (p !== false) {
|
||||||
|
const id = layer.id();
|
||||||
|
if (id !== 'artboard') {
|
||||||
|
Module.setProps(p, layer.id());
|
||||||
|
} else {
|
||||||
|
Module.setArtboardProps(p, layer.id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const Record = function(tp) {
|
const Record = function(tp) {
|
||||||
|
|
||||||
const hot = {};
|
const hot = {};
|
||||||
let isRecording = false;
|
let isRecording = false;
|
||||||
|
const buffy = new LiveBuffer();
|
||||||
|
const liveUpdater = new LiveUpdater(tp, buffy);
|
||||||
|
|
||||||
|
const isHot = (layerID, propTitle) => {
|
||||||
|
return hot.hasOwnProperty(layerID)
|
||||||
|
&& hot[layerID].hasOwnProperty(propTitle);
|
||||||
|
};
|
||||||
|
const makeHot = (layerID, propTitle) => {
|
||||||
|
if (!isHot(layerID, propTitle)) {
|
||||||
|
if (!hot.hasOwnProperty(layerID)) {
|
||||||
|
hot[layerID] = {};
|
||||||
|
}
|
||||||
|
hot[layerID][propTitle] = {
|
||||||
|
recording: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const button = tp
|
||||||
|
.getPanelPropContainer(propTitle)
|
||||||
|
.querySelector('.recordButton');
|
||||||
|
if (button !== null) {
|
||||||
|
button.classList.add('active');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//const makeNotHot = (layerID, propTitle) => {
|
||||||
|
//if (isHot(layerID, propTitle)) {
|
||||||
|
//// whatever
|
||||||
|
//}
|
||||||
|
//};
|
||||||
|
|
||||||
const addRecordButton = (layer, propTitle) => {
|
const addRecordButton = (layer, propTitle) => {
|
||||||
const panel = tp.getPanel();
|
const panel = tp.getPanel();
|
||||||
|
@ -22,24 +178,24 @@ const Record = function(tp) {
|
||||||
button.innerHTML = `<img src="/web/assets/record.svg" alt="record" />`;
|
button.innerHTML = `<img src="/web/assets/record.svg" alt="record" />`;
|
||||||
container.append(button);
|
container.append(button);
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
if (!hot.hasOwnProperty(layer.id())) {
|
if(isRecording) {
|
||||||
hot[layer.id()] = {};
|
|
||||||
}
|
|
||||||
if (!hot[layer.id()].hasOwnProperty(propTitle)) {
|
|
||||||
hot[layer.id()][propTitle] = {
|
|
||||||
recording: [],
|
|
||||||
};
|
|
||||||
button.classList.add('active');
|
|
||||||
startRecording();
|
|
||||||
} else {
|
|
||||||
stopRecording();
|
stopRecording();
|
||||||
delete hot[layer.id()][propTitle];
|
} else {
|
||||||
if (Object.keys(hot[layer.id()]).length === 0) {
|
Object.keys(audio.mapping)
|
||||||
delete hot[layer.id()];
|
.forEach((layerID) => {
|
||||||
}
|
if (getLayer(layerID).isSelected()) {
|
||||||
button.classList.remove('active');
|
Object.keys(audio.mapping[layerID])
|
||||||
|
.forEach((propTitle) => {
|
||||||
|
makeHot(layerID, propTitle);
|
||||||
|
});
|
||||||
|
buffy.register(layerID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
startRecording();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.log("Record::addRecordButton",
|
||||||
|
`added a record button for ${propTitle}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Record::addRecordButton",
|
console.log("Record::addRecordButton",
|
||||||
|
@ -127,12 +283,21 @@ const Record = function(tp) {
|
||||||
// NOTE: layerID is not actually used atm
|
// NOTE: layerID is not actually used atm
|
||||||
// and should be the layer anyways
|
// and should be the layer anyways
|
||||||
const input_clone = cloneInput(layerID, propTitle);
|
const input_clone = cloneInput(layerID, propTitle);
|
||||||
|
let lastPosition = buffy.NO_TIME;
|
||||||
if (input_clone !== null) {
|
if (input_clone !== null) {
|
||||||
input_clone.addEventListener('change', (e) => {
|
input_clone.addEventListener('change', (e) => {
|
||||||
|
const position = tp.sheet.sequence.position;
|
||||||
|
const value = parseFloat(input_clone.value);
|
||||||
hot[layerID][propTitle].recording.push({
|
hot[layerID][propTitle].recording.push({
|
||||||
position: tp.sheet.sequence.position,
|
position,
|
||||||
value: parseFloat(input_clone.value),
|
value,
|
||||||
});
|
});
|
||||||
|
const recording = {
|
||||||
|
[propTitle]: value,
|
||||||
|
};
|
||||||
|
buffy.addValues(layerID, recording, position, lastPosition);
|
||||||
|
liveUpdater.immediateUpdate(layer, recording);
|
||||||
|
lastPosition = position;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('whoops input_clone is null');
|
console.log('whoops input_clone is null');
|
||||||
|
@ -144,32 +309,70 @@ const Record = function(tp) {
|
||||||
isRecording = true;
|
isRecording = true;
|
||||||
};
|
};
|
||||||
const stopRecording = () => {
|
const stopRecording = () => {
|
||||||
console.log('stoprecording');
|
return new Promise((resolve) => {
|
||||||
const layerKeys = Object.keys(hot);
|
console.log('stoprecording');
|
||||||
console.log({layerKeys});
|
const layerKeys = Object.keys(hot);
|
||||||
layerKeys.forEach((layerID) => {
|
console.log('stopRecording', 'layerKeys', {
|
||||||
console.log(layerID);
|
layerKeys
|
||||||
const layer = getLayer(layerID);
|
}, 'hot', JSON.stringify(hot));
|
||||||
layer.updateValuesViaTheatre(true);
|
const promises = [];
|
||||||
const propTitles = Object.keys(hot[layerID]);
|
promises.push(() => {
|
||||||
const keyframes = [];
|
return new Promise((subResolve) => {
|
||||||
propTitles.forEach((propTitle) => {
|
const audioOptionsButtons = tp.getPanel()
|
||||||
console.log(propTitle);
|
.querySelectorAll(`.audioButton.active`);
|
||||||
// NOTE: layerID is not actually used atm
|
if (audioOptionsButtons !== null) {
|
||||||
// and should be the layer anyways
|
audioOptionsButtons.forEach((audioOptionsButton) => {
|
||||||
uncloneInput(layerID, propTitle);
|
audioOptionsButton.click();
|
||||||
console.log('should have uncloned input for ' + propTitle);
|
});
|
||||||
keyframes.push({
|
}
|
||||||
path: [propTitle],
|
subResolve();
|
||||||
keyframes: hot[layerID][propTitle].recording,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
layerKeys.forEach((layerID) => {
|
||||||
console.log('adding the keyframes now because we wnat it to happen right now please');
|
console.log('stopRecording', layerID);
|
||||||
tp.addKeyframes(layer, keyframes);
|
const layer = getLayer(layerID);
|
||||||
}, 2000);
|
const propTitles = Object.keys(hot[layerID]);
|
||||||
|
const keyframes = [];
|
||||||
|
propTitles.forEach((propTitle) => {
|
||||||
|
console.log('stopRecording', propTitle);
|
||||||
|
// NOTE: layerID is not actually used atm
|
||||||
|
// and should be the layer anyways
|
||||||
|
uncloneInput(layerID, propTitle);
|
||||||
|
console.log('stopRecording', 'should have uncloned input for ' + propTitle);
|
||||||
|
keyframes.push({
|
||||||
|
path: [propTitle],
|
||||||
|
keyframes: hot[layerID][propTitle].recording,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//setTimeout(() => {
|
||||||
|
console.log('stopRecording', 'adding the keyframes now because we wnat it to happen right now please', keyframes);
|
||||||
|
promises.push(() => {
|
||||||
|
return new Promise((subResolve) => {
|
||||||
|
tp.setKeyframes(layer, keyframes).then(() => {
|
||||||
|
layer.updateValuesViaTheatre(true);
|
||||||
|
subResolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
//}, 2000);
|
||||||
|
});
|
||||||
|
sequencialPromises(promises, () => {
|
||||||
|
Object.keys(hot).forEach((layerID) => {
|
||||||
|
buffy.deregister(layerID);
|
||||||
|
Object.keys(hot[layerID]).forEach((propTitle) => {
|
||||||
|
delete hot[layerID][propTitle];
|
||||||
|
if (Object.keys(hot[layerID]).length === 0) {
|
||||||
|
delete hot[layerID];
|
||||||
|
}
|
||||||
|
const button = tp.getPanel().querySelector(`.recordButton${propTitle}`);
|
||||||
|
button.classList.remove('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log('stopRecording', 'absolutely stopped recording');
|
||||||
|
isRecording = false;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
isRecording = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// public
|
// public
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
clone,
|
clone,
|
||||||
getParents,
|
getParents,
|
||||||
arraysEqual,
|
arraysEqual,
|
||||||
|
sequencialPromises,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
|
||||||
//import {
|
//import {
|
||||||
|
@ -140,16 +141,53 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
}
|
}
|
||||||
return t.parentElement.querySelector('[title="Sequence this prop"]');
|
return t.parentElement.querySelector('[title="Sequence this prop"]');
|
||||||
};
|
};
|
||||||
// no idea how to delete keyframes
|
|
||||||
// so we can only add
|
const setSequenced = (propTitle, sequenced) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const contextItem = sequenced ? 'sequence' : 'make static';
|
||||||
|
const antiContextItem = sequenced ? 'make static' : 'sequence';
|
||||||
|
|
||||||
|
const finishedSequencedEvent = (e) => {
|
||||||
|
tp.getPanel().removeEventListener('injected', finishedSequencedEvent);
|
||||||
|
console.log('debug FINISHED SEQUENCED EVENT', e, propTitle);
|
||||||
|
resolve(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickContextMenu = () => {
|
||||||
|
let done = false;
|
||||||
|
tp.getPanelPropTitle(propTitle).removeEventListener('contextmenu', clickContextMenu);
|
||||||
|
tp.shadowRoot.querySelectorAll('ul li span').forEach((s) => {
|
||||||
|
if (s.innerHTML.toLowerCase() === contextItem.toLowerCase()) {
|
||||||
|
tp.getPanel().addEventListener('injected', finishedSequencedEvent);
|
||||||
|
s.click();
|
||||||
|
console.log('debug click');
|
||||||
|
done = true;
|
||||||
|
} else if (s.innerHTML.toLowerCase() === antiContextItem.toLowerCase()) {
|
||||||
|
done = true;
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!done) {
|
||||||
|
setTimeout(() => {
|
||||||
|
clickContextMenu();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getPanelPropTitle(propTitle).addEventListener('contextmenu', clickContextMenu);
|
||||||
|
getPanelPropTitle(propTitle).dispatchEvent(new Event('contextmenu'));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const addKeyframes = (layer, keyframes) => {
|
const addKeyframes = (layer, keyframes) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (!Array.isArray(keyframes)) {
|
if (!Array.isArray(keyframes)) {
|
||||||
|
resolve(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const existingKeyframes = getKeyframes(layer);
|
const existingKeyframes = getKeyframes(layer);
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const ms = config.tp.addKeyframesTimeout_s * 1000;
|
const ms = 0;//config.tp.addKeyframesTimeout_s * 1000;
|
||||||
keyframes.forEach((k) => {
|
keyframes.forEach((k) => {
|
||||||
let prop = layer.theatreObject.props;
|
let prop = layer.theatreObject.props;
|
||||||
for (let i = 0; i < k.path.length; i++) {
|
for (let i = 0; i < k.path.length; i++) {
|
||||||
|
@ -159,12 +197,23 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
// NOTE: can we sequence values without pretend clicking?
|
// NOTE: can we sequence values without pretend clicking?
|
||||||
const sequenceButton = getSequenceButton(k.path);
|
const sequenceButton = getSequenceButton(k.path);
|
||||||
if (sequenceButton !== null) {
|
if (sequenceButton !== null) {
|
||||||
promises.push(new Promise((subResolve) => {
|
promises.push(() => { return new Promise((subResolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
sequenceButton.click();
|
sequenceButton.click();
|
||||||
subResolve();
|
const detectSE = (e) => {
|
||||||
}, ms * promises.length);
|
if (e.detail.panelID === layer.id()) {
|
||||||
}));
|
window.removeEventListener('sequenceEvent',detectSE);
|
||||||
|
console.log('received sequenceEvent',e);
|
||||||
|
const f = (e) => {
|
||||||
|
tp.getPanel().removeEventListener('injected', f);
|
||||||
|
subResolve();
|
||||||
|
};
|
||||||
|
tp.getPanel().addEventListener('injected', f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('sequenceEvent', detectSE);
|
||||||
|
}, ms);// * promises.length);
|
||||||
|
})});
|
||||||
} else {
|
} else {
|
||||||
//console.error(k.path, 'did not find sequence button');
|
//console.error(k.path, 'did not find sequence button');
|
||||||
// is (probably) already sequenced
|
// is (probably) already sequenced
|
||||||
|
@ -191,7 +240,7 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!alreadyThere) {
|
if (!alreadyThere) {
|
||||||
promises.push(new Promise((subResolve) => {
|
promises.push(() => { return new Promise((subResolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tp.sheet.sequence.position = keyframe.position;
|
tp.sheet.sequence.position = keyframe.position;
|
||||||
this.studio.transaction(({
|
this.studio.transaction(({
|
||||||
|
@ -200,22 +249,46 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
set(prop, keyframe.value);
|
set(prop, keyframe.value);
|
||||||
subResolve();
|
subResolve();
|
||||||
});
|
});
|
||||||
}, ms * promises.length);
|
}, ms);// * promises.length);
|
||||||
}));
|
})});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
promises.push(new Promise((subResolve) => {
|
promises.push(() => { return new Promise((subResolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
tp.sheet.sequence.position = position;
|
tp.sheet.sequence.position = position;
|
||||||
subResolve();
|
subResolve();
|
||||||
}, ms * promises.length);
|
}, ms);// * promises.length);
|
||||||
}));
|
})});
|
||||||
});
|
|
||||||
Promise.all(promises).then(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
});
|
||||||
|
sequencialPromises(promises, resolve);
|
||||||
|
//Promise.all(promises).then(() => {
|
||||||
|
//resolve();
|
||||||
|
//});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const setKeyframes = (layer, keyframes) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!Array.isArray(keyframes)) {
|
||||||
|
resolve(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const promises = [];
|
||||||
|
keyframes.forEach((k) => {
|
||||||
|
promises.push(new Promise((subResolve) => {
|
||||||
|
const propTitle = k.path.join('.');
|
||||||
|
setSequenced(propTitle, false)
|
||||||
|
.then(subResolve);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
Promise
|
||||||
|
.all(promises)
|
||||||
|
.then(() => {
|
||||||
|
addKeyframes(layer, keyframes)
|
||||||
|
.then(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const friendlySequenceNames = () => {
|
const friendlySequenceNames = () => {
|
||||||
const sequencePanelLeft = tp.getSequencePanelLeft();
|
const sequencePanelLeft = tp.getSequencePanelLeft();
|
||||||
let doItAgain = true;
|
let doItAgain = true;
|
||||||
|
@ -249,9 +322,11 @@ const TheatrePlay = function(autoInit = false) {
|
||||||
};
|
};
|
||||||
|
|
||||||
//public
|
//public
|
||||||
|
this.setSequenced = setSequenced;
|
||||||
this.friendlySequenceNames = friendlySequenceNames;
|
this.friendlySequenceNames = friendlySequenceNames;
|
||||||
this.getKeyframes = getKeyframes;
|
this.getKeyframes = getKeyframes;
|
||||||
this.addKeyframes = addKeyframes;
|
this.addKeyframes = addKeyframes;
|
||||||
|
this.setKeyframes = setKeyframes;
|
||||||
this.theatreObjects = theatreObjects;
|
this.theatreObjects = theatreObjects;
|
||||||
this.addObject = (name, props, onValuesChange) => {
|
this.addObject = (name, props, onValuesChange) => {
|
||||||
const obj = this.sheet.object(name, props);
|
const obj = this.sheet.object(name, props);
|
||||||
|
|
|
@ -395,6 +395,15 @@ const isMobile = () => {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sequencialPromises = async (iterable, callback = false) => {
|
||||||
|
for (const x of iterable) {
|
||||||
|
await x();
|
||||||
|
}
|
||||||
|
if (callback !== false) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -415,4 +424,5 @@ export {
|
||||||
arraysEqual,
|
arraysEqual,
|
||||||
mapValue,
|
mapValue,
|
||||||
isMobile,
|
isMobile,
|
||||||
|
sequencialPromises,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue