mute, letterDelay and other fixes

This commit is contained in:
jrkb 2023-10-13 16:24:34 +02:00
parent 748af243fa
commit 0ca73bca05
4 changed files with 158 additions and 223 deletions

View file

@ -32,7 +32,7 @@ const Audio = function(tp, record) {
heading.textContent = "CLICK HERE TO START"; heading.textContent = "CLICK HERE TO START";
// an array of possible sync options. // an array of possible sync options.
const audio_sync_options = ['volume', 'pitch', 'frequency']; const audio_sync_options = ['volume', 'pitch', 'clarity'];
// could also be an enum // could also be an enum
// like that // like that
//const AudioSyncOptions = Object.freeze({ //const AudioSyncOptions = Object.freeze({
@ -139,8 +139,8 @@ const Audio = function(tp, record) {
b.min_out = mm[0]; b.min_out = mm[0];
b.max_out = mm[1]; b.max_out = mm[1];
const a = new AudioMappingOptions(); const a = new AudioMappingOptions();
a.min_out = mm[0]; a.min_out = 1.0; // NOTE: dirty, dirty
a.max_out = mm[1]; a.max_out = 1.0; // hardcoded value, you
return [{r}, {g}, {b}, {a}]; return [{r}, {g}, {b}, {a}];
} else { } else {
const o = new AudioMappingOptions(); const o = new AudioMappingOptions();
@ -219,10 +219,11 @@ const Audio = function(tp, record) {
const createAudioOptions = (layer, propTitle, container) => { const createAudioOptions = (layer, propTitle, container) => {
const mappingOptions = mapping[layer.id()][propTitle]; const mappingOptions = mapping[layer.id()][propTitle];
let hasLetterDelay = config let hasLetterDelay = //false;
config
.layer.letterDelayProps .layer.letterDelayProps
.indexOf(propTitle.split('.')[0]) >= 0 .indexOf(propTitle.split('.')[0]) >= 0 && propTitle.indexOf('color') < 0;
&& tp.isSequenced([...[layer.id()], ...propTitle.split('.')]); //&& tp.isSequenced([...[layer.id()], ...propTitle.split('.')]);
const panel = tp.getPanel(); const panel = tp.getPanel();
if (!areMutationsObserved) { if (!areMutationsObserved) {
mutationObserver.observe(panel, { childList: true, subtree: true }); mutationObserver.observe(panel, { childList: true, subtree: true });
@ -273,6 +274,7 @@ const Audio = function(tp, record) {
mappingOptions.letterDelay = typeof ld.value === 'number' ? ld.value : parseInt(ld.value); mappingOptions.letterDelay = typeof ld.value === 'number' ? ld.value : parseInt(ld.value);
} }
mappingOptions.source = panel.querySelector(toCssClass(`audio_source${propTitle}`,'#')).value; mappingOptions.source = panel.querySelector(toCssClass(`audio_source${propTitle}`,'#')).value;
mappingOptions.muted = panel.querySelector(toCssClass(`audio_mute${propTitle}`,'#')).checked;
}; };
const source_Dom = document.createElement('select'); const source_Dom = document.createElement('select');
@ -286,12 +288,27 @@ const Audio = function(tp, record) {
if (file[0] !== '.') { if (file[0] !== '.') {
const source_file = document.createElement('option'); const source_file = document.createElement('option');
source_file.value = file; source_file.value = file;
if (file.length > config.audio.maxFilenameLength) {
source_file.innerHTML = file.substr(0,6) + '..' + file.substr(file.length - 6, 6);
} else {
source_file.innerHTML = file; source_file.innerHTML = file;
}
source_Dom.append(source_file); source_Dom.append(source_file);
} }
}); });
audioOptions.append(source_Dom); audioOptions.append(source_Dom);
const muteDom = document.createElement('input');
const muteDom_label = document.createElement('label');
muteDom.id = toCssClass(`audio_mute${propTitle}`);
muteDom.name = toCssClass(`audio_mute${propTitle}`);
muteDom.type = 'checkbox';
muteDom.checked = true;
muteDom_label.for = toCssClass(`audio_mute${propTitle}`);
muteDom_label.innerHTML = 'muted';
audioOptions.append(muteDom);
audioOptions.append(muteDom_label);
const min_max_Dom = document.createElement('div'); const min_max_Dom = document.createElement('div');
min_max_Dom.classList.add('audio_min_max'); min_max_Dom.classList.add('audio_min_max');
const min_Cont = document.createElement('div'); const min_Cont = document.createElement('div');
@ -412,6 +429,7 @@ const Audio = function(tp, record) {
fft_Dom.append(fft_selectDom); fft_Dom.append(fft_selectDom);
audioOptions.append(fft_Dom); audioOptions.append(fft_Dom);
source_Dom.addEventListener('change', updateMappingOptions); source_Dom.addEventListener('change', updateMappingOptions);
muteDom.addEventListener('change', updateMappingOptions);
min_inputDom.addEventListener('change', updateMappingOptions); min_inputDom.addEventListener('change', updateMappingOptions);
max_inputDom.addEventListener('change', updateMappingOptions); max_inputDom.addEventListener('change', updateMappingOptions);
smoothing_inputDom.addEventListener('change', updateMappingOptions); smoothing_inputDom.addEventListener('change', updateMappingOptions);
@ -609,10 +627,10 @@ const Audio = function(tp, record) {
} }
}); });
}; };
const audioSourceCombo = {}; const audioSourceCombos = {};
const readAudioFiles = () => { const readAudioFiles = () => {
FS.readdir(config.fs.idbfsAudioDir).forEach((file) => { FS.readdir(config.fs.idbfsAudioDir).forEach((file) => {
if (file.indexOf('.') !== 0 && !audioSourceCombo.hasOwnProperty(file)) { if (file.indexOf('.') !== 0 && !audioSourceCombos.hasOwnProperty(file)) {
const audioElement = document.createElement('audio'); const audioElement = document.createElement('audio');
audioElement.classList.add('invisible'); audioElement.classList.add('invisible');
audioElement.classList.add('audio_file'); audioElement.classList.add('audio_file');
@ -641,12 +659,12 @@ const Audio = function(tp, record) {
audioElement.loop = true; audioElement.loop = true;
const source = audioCtx.createMediaElementSource(audioElement); const source = audioCtx.createMediaElementSource(audioElement);
source.connect(audioCtx.destination); const gain = audioCtx.createGain();
const analyser = audioCtx.createAnalyser(); gain.gain.value = 0;
analyser.minDecibels = -90; source.connect(gain);
analyser.maxDecibels = -10; gain.connect(audioCtx.destination);
analyser.smoothingTimeConstant = 0.85; //source.connect(audioCtx.destination);
analyser.fftSize = config.audio.fftBandsAnalysed; const analyser = new AnalyserNode(audioCtx, config.audio.analyser);
const bufferLength = analyser.frequencyBinCount / 2; const bufferLength = analyser.frequencyBinCount / 2;
const dataArray = new Uint8Array(bufferLength); const dataArray = new Uint8Array(bufferLength);
@ -654,7 +672,9 @@ const Audio = function(tp, record) {
audioElement.play(); audioElement.play();
audioSourceCombo[file] = { audioSourceCombos[file] = {
gain,
source,
dataArray, dataArray,
analyser, analyser,
audioElement, audioElement,
@ -709,25 +729,22 @@ const Audio = function(tp, record) {
// window. is needed otherwise Safari explodes // window. is needed otherwise Safari explodes
audioCtx = new(window.AudioContext || window.webkitAudioContext)(); audioCtx = new(window.AudioContext || window.webkitAudioContext)();
const voiceSelect = audioDom.querySelector("#voice"); const voiceSelect = audioDom.querySelector("#voice");
let source;
let stream;
// Grab the mute button to use below // Grab the mute button to use below
const mute = audioDom.querySelector(".mute"); const mute = audioDom.querySelector(".mute");
// Set up the different audio nodes we will use for the app // Set up the different audio nodes we will use for the app
const analyser = audioCtx.createAnalyser(); {
analyser.minDecibels = -90; const analyser = new AnalyserNode(audioCtx, config.audio.analyser);
analyser.maxDecibels = -10;
analyser.smoothingTimeConstant = 0.85;
analyser.fftSize = config.audio.fftBandsAnalysed;
const bufferLength = analyser.frequencyBinCount / 2; const bufferLength = analyser.frequencyBinCount / 2;
audioSourceCombo['microphone'] = { audioSourceCombos['microphone'] = {
// source: see below when we actually get the microphone
analyser, analyser,
dataArray: new Uint8Array(bufferLength), dataArray: new Uint8Array(bufferLength),
audioElement: null, audioElement: null,
}; };
}
readAudioFiles(); readAudioFiles();
@ -754,34 +771,6 @@ const Audio = function(tp, record) {
return curve; return curve;
} }
// Grab audio track via XHR for convolver node
let soundSource;
const ajaxRequest = new XMLHttpRequest();
ajaxRequest.open(
"GET",
"https://mdn.github.io/voice-change-o-matic/audio/concert-crowd.ogg",
true
);
ajaxRequest.responseType = "arraybuffer";
ajaxRequest.onload = function() {
const audioData = ajaxRequest.response;
audioCtx.decodeAudioData(
audioData,
function(buffer) {
soundSource = audioCtx.createBufferSource();
},
function(e) {
console.log("Audio::audioCtx.decodeAudioData", "Error with decoding audio data" + e.err);
}
);
};
ajaxRequest.send();
// Set up canvas context for visualizer // Set up canvas context for visualizer
const canvas = audioDom.querySelector(".visualizer"); const canvas = audioDom.querySelector(".visualizer");
const canvasCtx = canvas.getContext("2d"); const canvasCtx = canvas.getContext("2d");
@ -801,8 +790,14 @@ const Audio = function(tp, record) {
navigator.mediaDevices navigator.mediaDevices
.getUserMedia(constraints) .getUserMedia(constraints)
.then(function(stream) { .then(function(stream) {
source = audioCtx.createMediaStreamSource(stream); const source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser); const gain = audioCtx.createGain();
gain.gain.value = 0;
source.connect(gain);
gain.connect(audioCtx.destination);
source.connect(audioSourceCombos['microphone'].analyser);
audioSourceCombos['microphone'].source = source;
audioSourceCombos['microphone'].gain = gain;
visualize(); visualize();
}) })
@ -819,11 +814,8 @@ const Audio = function(tp, record) {
const w = config.audio.fftBandsUsed; const w = config.audio.fftBandsUsed;
const h = config.audio.fftHeight; const h = config.audio.fftHeight;
const verticalFactor = h / 256.0; const verticalFactor = h / 256.0;
const bufferLengthAlt = analyser.frequencyBinCount / 2;
// See comment above for Float32Array() // See comment above for Float32Array()
const dataArrayAlt = new Uint8Array(bufferLengthAlt);
let canvasKeys = Object.keys(canvasCombos); let canvasKeys = Object.keys(canvasCombos);
for (let i = 0; i < canvasKeys.length; i++) { for (let i = 0; i < canvasKeys.length; i++) {
@ -853,7 +845,7 @@ const Audio = function(tp, record) {
const sh = (m.max_in - m.min_in) * verticalFactor; const sh = (m.max_in - m.min_in) * verticalFactor;
canvasCombos[k][1].fillStyle = "rgb(80, 80, 80)"; // AUDIO COLOR canvasCombos[k][1].fillStyle = "rgb(80, 80, 80)"; // AUDIO COLOR
canvasCombos[k][1].fillRect(sx, sy, sw, sh); canvasCombos[k][1].fillRect(sx, sy, sw, sh);
} else if (m.sync === 'pitch') { } else if (m.sync === 'pitch' || m.sync === 'clarity') {
const sx = m.min_freq; const sx = m.min_freq;
const sw = m.max_freq - m.min_freq; const sw = m.max_freq - m.min_freq;
const sy = 0; const sy = 0;
@ -863,13 +855,18 @@ const Audio = function(tp, record) {
} }
}); });
//analyser.getByteFrequencyData(dataArrayAlt);
const usedSourceCombos = []; const usedSourceCombos = [];
const analysedResults = {}; const analysedResults = {};
const unmuted = [];
Object.keys(mapping).forEach((layerID) => { Object.keys(mapping).forEach((layerID) => {
Object.keys(mapping[layerID]).forEach((propTitle) => { Object.keys(mapping[layerID]).forEach((propTitle) => {
const m = mapping[layerID][propTitle]; const m = mapping[layerID][propTitle];
const source = m.source; const source = m.source;
if (!m.muted) {
if (unmuted.indexOf(source) < 0) {
unmuted.push(source);
}
}
if (usedSourceCombos.indexOf(source) < 0) { if (usedSourceCombos.indexOf(source) < 0) {
usedSourceCombos.push(source); usedSourceCombos.push(source);
analysedResults[source] = { analysedResults[source] = {
@ -887,8 +884,8 @@ const Audio = function(tp, record) {
analysedResults[source].mappings.push(m); analysedResults[source].mappings.push(m);
}); });
}); });
Object.keys(audioSourceCombo).forEach((k) => { Object.keys(audioSourceCombos).forEach((k) => {
const asc = audioSourceCombo[k]; const asc = audioSourceCombos[k];
if (asc.audioElement !== null) { if (asc.audioElement !== null) {
if (usedSourceCombos.indexOf(k) >= 0) { if (usedSourceCombos.indexOf(k) >= 0) {
if (positionRollover || asc.audioElement.paused) { if (positionRollover || asc.audioElement.paused) {
@ -899,9 +896,14 @@ const Audio = function(tp, record) {
asc.audioElement.pause(); asc.audioElement.pause();
} }
} }
if (unmuted.indexOf(k) < 0) {
asc.gain.gain.value = 0;
} else {
asc.gain.gain.value = 1;
}
}); });
usedSourceCombos.forEach((source) => { usedSourceCombos.forEach((source) => {
const afs = audioSourceCombo[source]; const afs = audioSourceCombos[source];
const r = analysedResults[source]; const r = analysedResults[source];
afs.analyser.getByteFrequencyData(afs.dataArray); afs.analyser.getByteFrequencyData(afs.dataArray);
for (let f = 0; f < w; f++) { for (let f = 0; f < w; f++) {
@ -914,16 +916,19 @@ const Audio = function(tp, record) {
r.max_ri += v * f; r.max_ri += v * f;
let fillStyle = 'rgb(200,200,200)'; let fillStyle = 'rgb(200,200,200)';
for (let k_i = 0; k_i < canvasKeys.length; k_i++) { for (let k_i = 0; k_i < canvasKeys.length; k_i++) {
// NOTE: this is not the most efficient way to do it
const k = canvasKeys[k_i]; const k = canvasKeys[k_i];
const x = f; const layerID = canvasCombos[k][2];
if (mapping[layerID][k].source === source) {
canvasCombos[k][1].fillStyle = fillStyle; canvasCombos[k][1].fillStyle = fillStyle;
canvasCombos[k][1].fillRect( canvasCombos[k][1].fillRect(
x, f,
h - (v * verticalFactor), h - (v * verticalFactor),
1, 1,
(v * verticalFactor) (v * verticalFactor)
); );
} }
}
analysedResults[source].mappings.forEach((m) => { analysedResults[source].mappings.forEach((m) => {
if (m.min_freq <= f && m.max_freq >= f) { if (m.min_freq <= f && m.max_freq >= f) {
m.total_v += v; m.total_v += v;
@ -952,7 +957,7 @@ const Audio = function(tp, record) {
canvasCombos[k][1].lineWidth = 1; // AUDIO COLOR canvasCombos[k][1].lineWidth = 1; // AUDIO COLOR
canvasCombos[k][1].strokeStyle = "rgb(255,255,255)"; // AUDIO COLOR canvasCombos[k][1].strokeStyle = "rgb(255,255,255)"; // AUDIO COLOR
canvasCombos[k][1].strokeRect(sx, sy, sw, sh); canvasCombos[k][1].strokeRect(sx, sy, sw, sh);
} else if (m.sync === 'pitch') { } else if (m.sync === 'pitch' || m.sync === 'clarity') {
const sx = m.min_freq; const sx = m.min_freq;
const sw = m.max_freq - m.min_freq; const sw = m.max_freq - m.min_freq;
const sy = 0; const sy = 0;
@ -964,50 +969,60 @@ const Audio = function(tp, record) {
} }
const propsToSet = []; const propsToSet = [];
getLayers().forEach((layer) => { Object.keys(mapping).forEach((layerID) => {
if (mapping.hasOwnProperty(layer.id())) { Object.keys(mapping[layerID]).forEach((propTitle) => {
Object.keys(mapping[layer.id()]).forEach((propTitle) => { const m = mapping[layerID][propTitle];
const m = mapping[layer.id()][propTitle];
switch (m.sync) { switch (m.sync) {
case 'volume': { case 'volume': {
let a = mapValue(m.max_v, m.min_in, m.max_in, m.min_out, m.max_out, true); let a = mapValue(m.max_v, m.min_in, m.max_in, 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: layerID,
id: layer.id(),
title: propTitle, title: propTitle,
value: m.value, value: m.value,
}); });
break; break;
} }
case 'pitch': { case 'pitch': {
const mi = config.audio.ignoreOutboundFrequencies ? m.max_i : max_i; const r = analysedResults[m.source];
const ri = config.audio.ignoreOutboundFrequencies ? m.max_ri : max_ri; const mi = config.audio.ignoreOutboundFrequencies ? m.max_i : r.max_i;
const ri = config.audio.ignoreOutboundFrequencies ? m.max_ri : r.max_ri;
const fi = config.audio.pitchCombineFrequencies ? ri : mi; const fi = config.audio.pitchCombineFrequencies ? ri : mi;
let a = mapValue(fi, m.min_freq, m.max_freq, m.min_out, m.max_out, true); let a = mapValue(fi, m.min_freq, m.max_freq, m.min_out, m.max_out, true);
if (!isNaN(a)) {
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: layerID,
id: layer.id(),
title: propTitle, title: propTitle,
value: m.value, value: m.value,
}); });
}
break; break;
} }
case 'clarity': {
const clarity = m.max_v / m.total_v;
const a = mapValue(clarity, 0.01, 0.05, m.min_out, m.max_out, true);
if (!isNaN(a)) {
m.value = m.value * m.smoothing + (1.0 - m.smoothing) * a;
propsToSet.push({
id: layerID,
title: propTitle,
value: m.value,
});
}
}
default: default:
break; break;
} }
if (m.letterDelay) { if (m.letterDelay) {
const pt = `letterDelays.${propTitle}`; const pt = `letterDelays.${propTitle}`;
propsToSet.push({ propsToSet.push({
layer, id: layerID,
id: layer.id(),
title: pt, title: pt,
value: m.letterDelay, value: m.letterDelay,
}); });
} }
}); });
}
}); });
if (propsToSet.length > 0 && frameCount % 2 === 0) { if (propsToSet.length > 0 && frameCount % 2 === 0) {
// this is when to monitor live // this is when to monitor live
@ -1036,6 +1051,7 @@ const Audio = function(tp, record) {
propsToSet.forEach((p) => { propsToSet.forEach((p) => {
const title = tp const title = tp
.getPanelPropTitle(p.title); .getPanelPropTitle(p.title);
const layer = getLayer(p.id);
if (title !== null) { if (title !== null) {
const inputElement = title const inputElement = title
@ -1050,10 +1066,10 @@ const Audio = function(tp, record) {
record.addValue(p.id, p.title, p.value, position); record.addValue(p.id, p.title, p.value, position);
if (p.title.indexOf('color') === 0) { if (p.title.indexOf('color') === 0) {
if (!config.audio.colorSeparateRGBA || p.title === 'color.a') { if (!config.audio.colorSeparateRGBA || p.title === 'color.a') {
record.liveUpdate(p.layer, position); record.liveUpdate(layer, position);
} }
} else { } else {
record.liveUpdate(p.layer, position); record.liveUpdate(layer, position);
} }
}); });
} }
@ -1070,102 +1086,6 @@ const Audio = function(tp, record) {
}; };
drawAlt(); drawAlt();
} }
const voiceChange = () => {
distortion.oversample = "4x";
biquadFilter.gain.setTargetAtTime(0, audioCtx.currentTime, 0);
const voiceSetting = voiceSelect.value;
if (echoDelay.isApplied()) {
echoDelay.discard();
}
// When convolver is selected it is connected back into the audio path
if (voiceSetting == "convolver") {
biquadFilter.disconnect(0);
biquadFilter.connect(convolver);
} else {
biquadFilter.disconnect(0);
biquadFilter.connect(gainNode);
if (voiceSetting == "distortion") {
distortion.curve = makeDistortionCurve(400);
} else if (voiceSetting == "biquad") {
biquadFilter.type = "lowshelf";
biquadFilter.frequency.setTargetAtTime(1000, audioCtx.currentTime, 0);
biquadFilter.gain.setTargetAtTime(25, audioCtx.currentTime, 0);
} else if (voiceSetting == "delay") {
echoDelay.apply();
} else if (voiceSetting == "off") {
console.log("Voice settings turned off");
}
}
}
function createEchoDelayEffect(audioContext) {
const delay = audioContext.createDelay(1);
const dryNode = audioContext.createGain();
const wetNode = audioContext.createGain();
const mixer = audioContext.createGain();
const filter = audioContext.createBiquadFilter();
delay.delayTime.value = 0.75;
dryNode.gain.value = 1;
wetNode.gain.value = 0;
filter.frequency.value = 1100;
filter.type = "highpass";
return {
apply: function() {
wetNode.gain.setValueAtTime(0.75, audioContext.currentTime);
},
discard: function() {
wetNode.gain.setValueAtTime(0, audioContext.currentTime);
},
isApplied: function() {
return wetNode.gain.value > 0;
},
placeBetween: function(inputNode, outputNode) {
inputNode.connect(delay);
delay.connect(wetNode);
wetNode.connect(filter);
filter.connect(delay);
inputNode.connect(dryNode);
dryNode.connect(mixer);
wetNode.connect(mixer);
mixer.connect(outputNode);
},
};
}
// Event listeners to change visualize and voice settings
visualSelect.onchange = function() {
window.cancelAnimationFrame(drawVisual);
visualize();
};
voiceSelect.onchange = function() {
voiceChange();
};
mute.onclick = voiceMute;
let previousGain;
function voiceMute() {
if (mute.id === "") {
previousGain = gainNode.gain.value;
gainNode.gain.value = 0;
mute.id = "activated";
mute.innerHTML = "Unmute";
} else {
gainNode.gain.value = previousGain;
mute.id = "";
mute.innerHTML = "Mute";
}
}
} }
} }
const deinit = () => { const deinit = () => {
@ -1193,7 +1113,7 @@ const Audio = function(tp, record) {
// debug // debug
this.canvasCombos = canvasCombos; this.canvasCombos = canvasCombos;
this.audioSourceCombo = audioSourceCombo; this.audioSourceCombos = audioSourceCombos;
}; };
export { export {

View file

@ -95,7 +95,14 @@ const config = {
'letterDelays': [0, 1000], 'letterDelays': [0, 1000],
}, },
ignoreProps: ['transformOrigin', 'fontFamily', 'text', 'mirror_x', 'mirror_y', 'mirror_xy', 'height'], ignoreProps: ['transformOrigin', 'fontFamily', 'text', 'mirror_x', 'mirror_y', 'mirror_xy', 'height'],
maxFilenameLength: 24,
defaultSmoothing: 0.7, defaultSmoothing: 0.7,
analyser: {
fftSize: 256 * 8,
minDecibels: -90,
maxDecibels: -10,
smoothingTimeConstant: 0.85,
},
fftBandsAnalysed: 256 * 8, fftBandsAnalysed: 256 * 8,
fftBandsUsed: 256 / 2, fftBandsUsed: 256 / 2,
fftHeight: 256 / 4, fftHeight: 256 / 4,

View file

@ -416,12 +416,20 @@ const listAvailableFontsAndAxes = () => {
window.listAvailableFontsAndAxes = listAvailableFontsAndAxes; window.listAvailableFontsAndAxes = listAvailableFontsAndAxes;
window.getFontsAndAxes = getFontsAndAxes; window.getFontsAndAxes = getFontsAndAxes;
window.getArtboard = () => {
return artboard;
};
window.getLayers = () => { window.getLayers = () => {
return layers; return layers;
}; };
window.getLayer = (layerID) => { window.getLayer = (layerID) => {
if (layerID === 'artboard') {
return artboard;
} else {
return layers.find((layer) => layer.id() === layerID); return layers.find((layer) => layer.id() === layerID);
}
}; };
window.moveLayerUp = (layerID) => { window.moveLayerUp = (layerID) => {
@ -432,10 +440,6 @@ window.moveLayerDown = (layerID) => {
layerOrder.moveDown(layerID); layerOrder.moveDown(layerID);
}; };
window.getArtboard = () => {
return artboard;
};
const addLayer = (autoInit = true) => { const addLayer = (autoInit = true) => {
const layerID = Module.addNewLayer(); const layerID = Module.addNewLayer();
const layer = new Layer(tp, layerID, fontsAndAxes, autoInit); const layer = new Layer(tp, layerID, fontsAndAxes, autoInit);

View file

@ -117,6 +117,7 @@ const LiveUpdater = function(tp, buffy) {
}; };
this.immediateUpdate = (layer, values) => { this.immediateUpdate = (layer, values) => {
const cv = clone(values); const cv = clone(values);
const ctv = clone(layer.theatreObject.value);
if (cv.hasOwnProperty('color.r')) { if (cv.hasOwnProperty('color.r')) {
cv['color'] = { cv['color'] = {
r: cv['color.r'], r: cv['color.r'],
@ -129,7 +130,10 @@ const LiveUpdater = function(tp, buffy) {
delete cv['color.b']; delete cv['color.b'];
delete cv['color.a']; delete cv['color.a'];
} }
const v = {...layer.theatreObject.value, ...cv}; flattenObject(cv, ['color']);
flattenObject(ctv, ['color']);
const v = {...ctv, ...cv};
deFlattenObject(v, ['color']);
const p = layer.values2cppProps(v); const p = layer.values2cppProps(v);
if (p !== false) { if (p !== false) {
const id = layer.id(); const id = layer.id();