From 4cb5742afb317cb3186bd0c72e81962b48b24e13 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Thu, 28 Sep 2023 16:41:11 +0200 Subject: [PATCH] simple generic recording, hook up audio --- bin/web/js/audio.js | 38 +++++++--- bin/web/js/config.js | 3 + bin/web/js/layer.js | 13 ++++ bin/web/js/main.js | 8 +- bin/web/js/record.js | 148 ++++++++++++++++++++++++++++++++++++- bin/web/js/theatre-play.js | 8 ++ 6 files changed, 200 insertions(+), 18 deletions(-) diff --git a/bin/web/js/audio.js b/bin/web/js/audio.js index ea49584..7c8dab8 100644 --- a/bin/web/js/audio.js +++ b/bin/web/js/audio.js @@ -145,7 +145,6 @@ const Audio = function(tp, record) { const bb = fft_Dom.getBoundingClientRect(); const x = e.clientX - bb.x; freq_down = mapValue(e.clientX, bb.x, bb.x + bb.width, 0, 256 * 8 / 2, true); - console.log('up',JSON.parse(JSON.stringify(e)), e); }); //removeAudioOptions(); @@ -182,8 +181,9 @@ const Audio = function(tp, record) { console.log("Audio::addAudioButton", `impossible! cannot find panelPropContainer for ${propTitle}`); } else if (container.querySelector('.audioButton') !== null) { - console.log("Audio::addAudioButton", - `already added an audio button for ${propTitle}`); + // this is super verbose, let's not log by default + //console.log("Audio::addAudioButton", + //`already added an audio button for ${propTitle}`); } else { const button = document.createElement('div'); button.classList.add('audioButton'); @@ -478,6 +478,7 @@ const Audio = function(tp, record) { 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; propsToSet.push({ + title: propTitle, prop: layer.theatreObject.props[propTitle], value: m.value, }); @@ -487,6 +488,7 @@ const Audio = function(tp, record) { 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; propsToSet.push({ + title: propTitle, prop: layer.theatreObject.props[propTitle], value: m.value, }); @@ -501,21 +503,33 @@ const Audio = function(tp, record) { } }); if (propsToSet.length > 0 && frameCount % 2 === 0) { - tp.studio.transaction(({ - set - }) => { - propsToSet.forEach((p) => { - set(p.prop, p.value, true); + if (!record.isRecording()) { + tp.studio.transaction(({ + set + }) => { + propsToSet.forEach((p) => { + set(p.prop, p.value, true); + }); }); - }); + } else { + propsToSet.forEach((p) => { + // TODO: this does not have to be queried + // but we could store it in a map/set/dictionary/array/object + const inputElement = tp + .getPanelPropContainer(p.title) + .querySelector('input.recording'); + + if (inputElement !== null) { + inputElement.value = p.value; + inputElement.dispatchEvent(new Event('change')); + } + }); + } } const panel = tp.getPanel(); const fft_images = panel.querySelectorAll('.audio_fft'); if (fft_images !== null) { const src = canvas.toDataURL(); - if (window.printDebug === true) { - console.log({canvas, src, fft_images, panel}, "DEBUG AUDIO"); - } fft_images.forEach((e) => { e.src = src; }); diff --git a/bin/web/js/config.js b/bin/web/js/config.js index ad719c6..07c6813 100644 --- a/bin/web/js/config.js +++ b/bin/web/js/config.js @@ -85,6 +85,9 @@ const config = { ignoreProps: ['transformOrigin', 'fontFamily', 'text', 'mirror_x', 'mirror_y', 'mirror_xy', 'fontVariationAxes', 'color'], defaultSmoothing: 0.7, }, + record: { + ignoreProps: ['fontVariationAxes','letterDelays','color'], + }, midi: { touchTimeThreshold_s: 0.5, smoothingMix: 0.1, diff --git a/bin/web/js/layer.js b/bin/web/js/layer.js index df3d77f..9b5913c 100644 --- a/bin/web/js/layer.js +++ b/bin/web/js/layer.js @@ -55,6 +55,7 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) { text: config.layer.defaultTexts[Math.floor(Math.random()*config.layer.defaultTexts.length)], }; + let updateValuesViaTheatre = true; let lastValues = {}; let fontsHashMap = {}; let sequenceEventBuffer = []; @@ -204,6 +205,8 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) { const onValuesChange = (values) => { if (values.dummy === true) return; + if (!updateValuesViaTheatre) + return; window.isRenderDirty = true; if (Object.keys(values).length > 1) { values.text = values.text.trim(); @@ -792,11 +795,18 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) { panel.addEventListener("mouseover", showBoundingBoxDivIfSelected); panel.addEventListener("mouseleave", hideBoundingBoxDiv); + // should we have an audio object, let's inject the buttons, etc if (typeof audio === 'object' && audio.hasOwnProperty('injectPanel')) { audio.injectPanel(this); } else { console.log('Layer::findInjectPanel', `cannot inject audio panel for ${this.id()} for some reason.`); } + // should we have a record object, let's inject the buttons, etc + if (typeof record === 'object' && record.hasOwnProperty('injectPanel')) { + record.injectPanel(this); + } else { + console.log('Layer::findInjectPanel', `cannot inject record panel for ${this.id()} for some reason.`); + } injectedPanel = true; const detail = {titles: Object.keys(panelPropTitles), containers: Object.keys(panelPropContainers)}; @@ -1036,6 +1046,9 @@ const Layer = function(tp, layerID, fontsAndAxes, autoInit = true) { return cppProps; }; this.onValuesChange = onValuesChange; + this.updateValuesViaTheatre = (doIt) => { + updateValuesViaTheatre = doIt; + }; this.prepareForDepartureFromThisBeautifulExperience = () => { this.hide(); hideBoundingBoxDiv(); diff --git a/bin/web/js/main.js b/bin/web/js/main.js index 19a25b1..0bc1e9e 100644 --- a/bin/web/js/main.js +++ b/bin/web/js/main.js @@ -72,8 +72,8 @@ const interactor = new Interactor(); const moduleFS = new ModuleFS(); window.moduleFS = moduleFS; const record = new Record(tp); -window.debug_record = record; -const audio = new Audio(tp, record); // possibly nicer if we pass tp instead of attaching to window +window.record = record; +const audio = new Audio(tp, record); window.audio = audio; window.panelFinderTimeout = false; @@ -410,6 +410,10 @@ window.getLayers = () => { return layers; }; +window.getLayer = (layerID) => { + return layers.find((layer) => layer.id() === layerID); +}; + window.moveLayerUp = (layerID) => { layerOrder.moveUp(layerID); }; diff --git a/bin/web/js/record.js b/bin/web/js/record.js index f6da507..53ca264 100644 --- a/bin/web/js/record.js +++ b/bin/web/js/record.js @@ -1,8 +1,9 @@ const Record = function(tp) { const hot = {}; + let isRecording = false; - const addRecordButton = (layer, propTitle, isActive) => { + const addRecordButton = (layer, propTitle) => { const panel = tp.getPanel(); const panelPropTitle = tp.getPanelPropTitle(propTitle); if (panelPropTitle !== null) { @@ -11,8 +12,9 @@ const Record = function(tp) { console.log("Record::addRecordButton", `impossible! cannot find panelPropContainer for ${propTitle}`); } else if (container.querySelector('.recordButton') !== null) { - console.log("Record::addRecordButton", - `already added an record button for ${propTitle}`); + // this is super verbose, let's not log by default + //console.log("Record::addRecordButton", + //`already added an record button for ${propTitle}`); } else { const button = document.createElement('div'); button.classList.add('recordButton'); @@ -24,9 +26,13 @@ const Record = function(tp) { hot[layer.id()] = {}; } if (!hot[layer.id()].hasOwnProperty(propTitle)) { - hot[layer.id()][propTitle] = {}; + hot[layer.id()][propTitle] = { + recording: [], + }; button.classList.add('active'); + startRecording(); } else { + stopRecording(); delete hot[layer.id()][propTitle]; if (Object.keys(hot[layer.id()]).length === 0) { delete hot[layer.id()]; @@ -41,8 +47,142 @@ const Record = function(tp) { } }; + const cloneInput = (layer, propTitle) => { + const panel = tp.getPanel(); + const panelPropTitle = tp.getPanelPropTitle(propTitle); + if (panelPropTitle !== null) { + const container = tp.getPanelPropContainer(panelPropTitle); + if (container === null) { + console.log("Record::cloneInput", + `impossible! cannot find panelPropContainer for ${propTitle}`); + } else if (container.querySelector('input.recording') !== null) { + console.log("Record::cloneInput", + `already cloned input for ${propTitle}`); + } else { + const input = container.querySelector('input'); + if (input === null) { + console.log("Record::cloneInput", + `uuh.. seems there is no input to clone for ${propTitle}`); + } else { + const input_clone = input.cloneNode(); + input_clone.classList.value = ''; + input_clone.classList.add('recording'); + input.parentNode.after(input_clone); + input.setAttribute('data-previousDisplay', input.style.display); + input.style.display = 'none'; + return input_clone; + } + } + } + return null; + }; + + const uncloneInput = (layer, propTitle) => { + const panel = tp.getPanel(); + const panelPropTitle = tp.getPanelPropTitle(propTitle); + if (panelPropTitle !== null) { + const container = tp.getPanelPropContainer(panelPropTitle); + if (container === null) { + console.log("Record::uncloneInput", + `impossible! cannot find panelPropContainer for ${propTitle}`); + } else if (container.querySelector('input.recording') === null) { + console.log("Record::uncloneInput", + `already uncloned input for ${propTitle}`); + } else { + const input = container.querySelector('input:not(.recording)'); + const input_clone = container.querySelector('input.recording'); + if (input === null ) { + console.log("Record::uncloneInput", + `uuh.. seems there is no input for ${propTitle}`); + } else if (input_clone === null ) { + console.log("Record::uncloneInput", + `uuh.. seems there is no input_clone for ${propTitle}`); + } else { + input_clone.remove(); + input.removeAttribute('data-previousDisplay'); + const previousInputDisplay = input.getAttribute('data-previousDisplay'); + input.style.display = previousInputDisplay; + } + } + } + }; + + const injectPanel = (layer) => { + const props = Object.keys(layer.theatreObject.value); + props.forEach((propTitle) => { + if (config.record.ignoreProps.indexOf(propTitle) < 0) { + addRecordButton(layer, propTitle); + } + }); + }; + + const startRecording = () => { + tp.sheet.sequence.pause(); + const layerKeys = Object.keys(hot); + layerKeys.forEach((layerID) => { + const layer = getLayer(layerID); + layer.updateValuesViaTheatre(false); + const propTitles = Object.keys(hot[layerID]); + propTitles.forEach((propTitle) => { + // NOTE: layerID is not actually used atm + // and should be the layer anyways + const input_clone = cloneInput(layerID, propTitle); + if (input_clone !== null) { + input_clone.addEventListener('change', (e) => { + hot[layerID][propTitle].recording.push({ + position: tp.sheet.sequence.position, + value: parseFloat(input_clone.value), + }); + }); + } else { + console.log('whoops input_clone is null'); + } + }); + tp.sheet.sequence.position = 0; + tp.sheet.sequence.play(); + }); + isRecording = true; + }; + const stopRecording = () => { + console.log('stoprecording'); + const layerKeys = Object.keys(hot); + console.log({layerKeys}); + layerKeys.forEach((layerID) => { + console.log(layerID); + const layer = getLayer(layerID); + layer.updateValuesViaTheatre(true); + const propTitles = Object.keys(hot[layerID]); + const keyframes = []; + propTitles.forEach((propTitle) => { + console.log(propTitle); + // NOTE: layerID is not actually used atm + // and should be the layer anyways + uncloneInput(layerID, propTitle); + console.log('should have uncloned input for ' + propTitle); + keyframes.push({ + path: [propTitle], + keyframes: hot[layerID][propTitle].recording, + }); + }); + setTimeout(() => { + console.log('adding the keyframes now because we wnat it to happen right now please'); + tp.addKeyframes(layer, keyframes); + }, 2000); + }); + isRecording = false; + }; + // public this.addRecordButton = addRecordButton; + this.getHot = () => { + return hot; + }; + this.isRecording = () => { + return isRecording; + }; + this.injectPanel = injectPanel; + this.startRecording = startRecording; + this.stopRecording = stopRecording; }; export { diff --git a/bin/web/js/theatre-play.js b/bin/web/js/theatre-play.js index 501ccc3..8c1e310 100644 --- a/bin/web/js/theatre-play.js +++ b/bin/web/js/theatre-play.js @@ -571,10 +571,18 @@ const TheatrePlay = function(autoInit = false) { } .audioButton{ width: 20px; + margin: 2px; } .audioButton.active{ background: green; } + .recordButton{ + width: 20px; + margin: 2px; + } + .recordButton.active{ + background: green; + } `; this.shadowRoot.appendChild(style); }