| | import { app } from "../../../scripts/app.js"; |
| |
|
| | app.registerExtension({ |
| | name: "KJNodes.jsnodes", |
| | async beforeRegisterNodeDef(nodeType, nodeData, app) { |
| | if(!nodeData?.category?.startsWith("KJNodes")) { |
| | return; |
| | } |
| | switch (nodeData.name) { |
| | case "ConditioningMultiCombine": |
| | nodeType.prototype.onNodeCreated = function () { |
| | this.cond_type = "CONDITIONING" |
| | this.inputs_offset = nodeData.name.includes("selective")?1:0 |
| | this.addWidget("button", "Update inputs", null, () => { |
| | if (!this.inputs) { |
| | this.inputs = []; |
| | } |
| | const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; |
| | if(target_number_of_inputs===this.inputs.length)return; |
| |
|
| | if(target_number_of_inputs < this.inputs.length){ |
| | for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) |
| | this.removeInput(i) |
| | } |
| | else{ |
| | for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) |
| | this.addInput(`conditioning_${i}`, this.cond_type) |
| | } |
| | }); |
| | } |
| | break; |
| | case "ImageBatchMulti": |
| | case "ImageAddMulti": |
| | case "ImageConcatMulti": |
| | nodeType.prototype.onNodeCreated = function () { |
| | this._type = "IMAGE" |
| | this.inputs_offset = nodeData.name.includes("selective")?1:0 |
| | this.addWidget("button", "Update inputs", null, () => { |
| | if (!this.inputs) { |
| | this.inputs = []; |
| | } |
| | const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; |
| | if(target_number_of_inputs===this.inputs.length)return; |
| |
|
| | if(target_number_of_inputs < this.inputs.length){ |
| | for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) |
| | this.removeInput(i) |
| | } |
| | else{ |
| | for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) |
| | this.addInput(`image_${i}`, this._type) |
| | } |
| | }); |
| | } |
| | break; |
| | case "MaskBatchMulti": |
| | nodeType.prototype.onNodeCreated = function () { |
| | this._type = "MASK" |
| | this.inputs_offset = nodeData.name.includes("selective")?1:0 |
| | this.addWidget("button", "Update inputs", null, () => { |
| | if (!this.inputs) { |
| | this.inputs = []; |
| | } |
| | const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; |
| | if(target_number_of_inputs===this.inputs.length)return; |
| |
|
| | if(target_number_of_inputs < this.inputs.length){ |
| | for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) |
| | this.removeInput(i) |
| | } |
| | else{ |
| | for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) |
| | this.addInput(`mask_${i}`, this._type) |
| | } |
| | }); |
| | } |
| | break; |
| | |
| | case "FluxBlockLoraSelect": |
| | nodeType.prototype.onNodeCreated = function () { |
| | this.addWidget("button", "Set all", null, () => { |
| | const userInput = prompt("Enter the values to set for widgets (e.g., s0,1,2-7=2.0, d0,1,2-7=2.0, or 1.0):", ""); |
| | if (userInput) { |
| | const regex = /([sd])?(\d+(?:,\d+|-?\d+)*?)?=(\d+(\.\d+)?)/; |
| | const match = userInput.match(regex); |
| | if (match) { |
| | const type = match[1]; |
| | const indicesPart = match[2]; |
| | const value = parseFloat(match[3]); |
| | |
| | let targetWidgets = []; |
| | if (type === 's') { |
| | targetWidgets = this.widgets.filter(widget => widget.name.includes("single")); |
| | } else if (type === 'd') { |
| | targetWidgets = this.widgets.filter(widget => widget.name.includes("double")); |
| | } else { |
| | targetWidgets = this.widgets; |
| | } |
| | |
| | if (indicesPart) { |
| | const indices = indicesPart.split(',').flatMap(part => { |
| | if (part.includes('-')) { |
| | const [start, end] = part.split('-').map(Number); |
| | return Array.from({ length: end - start + 1 }, (_, i) => start + i); |
| | } |
| | return Number(part); |
| | }); |
| | |
| | for (const index of indices) { |
| | if (index < targetWidgets.length) { |
| | targetWidgets[index].value = value; |
| | } |
| | } |
| | } else { |
| | |
| | for (const widget of targetWidgets) { |
| | widget.value = value; |
| | } |
| | } |
| | } else if (!isNaN(parseFloat(userInput))) { |
| | |
| | const value = parseFloat(userInput); |
| | for (const widget of this.widgets) { |
| | widget.value = value; |
| | } |
| | } else { |
| | alert("Invalid input format. Please use the format s0,1,2-7=2.0, d0,1,2-7=2.0, or 1.0"); |
| | } |
| | } else { |
| | alert("Invalid input. Please enter a value."); |
| | } |
| | }); |
| | }; |
| | break; |
| |
|
| | case "GetMaskSizeAndCount": |
| | const onGetMaskSizeConnectInput = nodeType.prototype.onConnectInput; |
| | nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) { |
| | const v = onGetMaskSizeConnectInput? onGetMaskSizeConnectInput.apply(this, arguments): undefined |
| | this.outputs[1]["name"] = "width" |
| | this.outputs[2]["name"] = "height" |
| | this.outputs[3]["name"] = "count" |
| | return v; |
| | } |
| | const onGetMaskSizeExecuted = nodeType.prototype.onExecuted; |
| | nodeType.prototype.onExecuted = function(message) { |
| | const r = onGetMaskSizeExecuted? onGetMaskSizeExecuted.apply(this,arguments): undefined |
| | let values = message["text"].toString().split('x').map(Number); |
| | this.outputs[1]["name"] = values[1] + " width" |
| | this.outputs[2]["name"] = values[2] + " height" |
| | this.outputs[3]["name"] = values[0] + " count" |
| | return r |
| | } |
| | break; |
| | |
| | case "GetImageSizeAndCount": |
| | const onGetImageSizeConnectInput = nodeType.prototype.onConnectInput; |
| | nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) { |
| | const v = onGetImageSizeConnectInput? onGetImageSizeConnectInput.apply(this, arguments): undefined |
| | this.outputs[1]["name"] = "width" |
| | this.outputs[2]["name"] = "height" |
| | this.outputs[3]["name"] = "count" |
| | return v; |
| | } |
| | const onGetImageSizeExecuted = nodeType.prototype.onExecuted; |
| | nodeType.prototype.onExecuted = function(message) { |
| | const r = onGetImageSizeExecuted? onGetImageSizeExecuted.apply(this,arguments): undefined |
| | let values = message["text"].toString().split('x').map(Number); |
| | this.outputs[1]["name"] = values[1] + " width" |
| | this.outputs[2]["name"] = values[2] + " height" |
| | this.outputs[3]["name"] = values[0] + " count" |
| | return r |
| | } |
| | break; |
| |
|
| | case "PreviewAnimation": |
| | const onPreviewAnimationConnectInput = nodeType.prototype.onConnectInput; |
| | nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) { |
| | const v = onPreviewAnimationConnectInput? onPreviewAnimationConnectInput.apply(this, arguments): undefined |
| | this.title = "Preview Animation" |
| | return v; |
| | } |
| | const onPreviewAnimationExecuted = nodeType.prototype.onExecuted; |
| | nodeType.prototype.onExecuted = function(message) { |
| | const r = onPreviewAnimationExecuted? onPreviewAnimationExecuted.apply(this,arguments): undefined |
| | let values = message["text"].toString(); |
| | this.title = "Preview Animation " + values |
| | return r |
| | } |
| | break; |
| |
|
| | case "VRAM_Debug": |
| | const onVRAM_DebugConnectInput = nodeType.prototype.onConnectInput; |
| | nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) { |
| | const v = onVRAM_DebugConnectInput? onVRAM_DebugConnectInput.apply(this, arguments): undefined |
| | this.outputs[3]["name"] = "freemem_before" |
| | this.outputs[4]["name"] = "freemem_after" |
| | return v; |
| | } |
| | const onVRAM_DebugExecuted = nodeType.prototype.onExecuted; |
| | nodeType.prototype.onExecuted = function(message) { |
| | const r = onVRAM_DebugExecuted? onVRAM_DebugExecuted.apply(this,arguments): undefined |
| | let values = message["text"].toString().split('x'); |
| | this.outputs[3]["name"] = values[0] + " freemem_before" |
| | this.outputs[4]["name"] = values[1] + " freemem_after" |
| | return r |
| | } |
| | break; |
| |
|
| | case "JoinStringMulti": |
| | const originalOnNodeCreated = nodeType.prototype.onNodeCreated || function() {}; |
| | nodeType.prototype.onNodeCreated = function () { |
| | originalOnNodeCreated.apply(this, arguments); |
| | |
| | this._type = "STRING"; |
| | this.inputs_offset = nodeData.name.includes("selective") ? 1 : 0; |
| | this.addWidget("button", "Update inputs", null, () => { |
| | if (!this.inputs) { |
| | this.inputs = []; |
| | } |
| | const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; |
| | if (target_number_of_inputs === this.inputs.length) return; |
| | |
| | if (target_number_of_inputs < this.inputs.length) { |
| | for (let i = this.inputs.length; i >= this.inputs_offset + target_number_of_inputs; i--) |
| | this.removeInput(i); |
| | } else { |
| | for (let i = this.inputs.length + 1 - this.inputs_offset; i <= target_number_of_inputs; ++i) |
| | this.addInput(`string_${i}`, this._type); |
| | } |
| | }); |
| | } |
| | break; |
| | case "SoundReactive": |
| | nodeType.prototype.onNodeCreated = function () { |
| | let audioContext; |
| | let microphoneStream; |
| | let animationFrameId; |
| | let analyser; |
| | let dataArray; |
| | let startRangeHz; |
| | let endRangeHz; |
| | let smoothingFactor = 0.5; |
| | let smoothedSoundLevel = 0; |
| | |
| | |
| | const updateWidgetValueInRealTime = () => { |
| | |
| | if (analyser && dataArray) { |
| | analyser.getByteFrequencyData(dataArray); |
| |
|
| | const startRangeHzWidget = this.widgets.find(w => w.name === "start_range_hz"); |
| | if (startRangeHzWidget) startRangeHz = startRangeHzWidget.value; |
| | const endRangeHzWidget = this.widgets.find(w => w.name === "end_range_hz"); |
| | if (endRangeHzWidget) endRangeHz = endRangeHzWidget.value; |
| | const smoothingFactorWidget = this.widgets.find(w => w.name === "smoothing_factor"); |
| | if (smoothingFactorWidget) smoothingFactor = smoothingFactorWidget.value; |
| |
|
| | |
| | const frequencyBinWidth = audioContext.sampleRate / analyser.fftSize; |
| | |
| | const startRangeIndex = Math.floor(startRangeHz / frequencyBinWidth); |
| | const endRangeIndex = Math.floor(endRangeHz / frequencyBinWidth); |
| |
|
| | |
| | const calculateAverage = (start, end) => { |
| | const sum = dataArray.slice(start, end).reduce((acc, val) => acc + val, 0); |
| | const average = sum / (end - start); |
| |
|
| | |
| | smoothedSoundLevel = (average * (1 - smoothingFactor)) + (smoothedSoundLevel * smoothingFactor); |
| | return smoothedSoundLevel; |
| | }; |
| | |
| | const soundLevel = calculateAverage(startRangeIndex, endRangeIndex); |
| | |
| | |
| |
|
| | const lowLevelWidget = this.widgets.find(w => w.name === "sound_level"); |
| | if (lowLevelWidget) lowLevelWidget.value = soundLevel; |
| |
|
| | animationFrameId = requestAnimationFrame(updateWidgetValueInRealTime); |
| | } |
| | }; |
| | |
| | |
| | const startMicrophoneCapture = () => { |
| | |
| | if (!audioContext) { |
| | audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| | |
| | console.log(`Sample rate: ${audioContext.sampleRate}Hz`); |
| | analyser = audioContext.createAnalyser(); |
| | analyser.fftSize = 2048; |
| | dataArray = new Uint8Array(analyser.frequencyBinCount); |
| | |
| | const lowRangeWidget = this.widgets.find(w => w.name === "low_range_hz"); |
| | if (lowRangeWidget) startRangeHz = lowRangeWidget.value; |
| | |
| | const midRangeWidget = this.widgets.find(w => w.name === "mid_range_hz"); |
| | if (midRangeWidget) endRangeHz = midRangeWidget.value; |
| | } |
| | |
| | navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { |
| | microphoneStream = stream; |
| | const microphone = audioContext.createMediaStreamSource(stream); |
| | microphone.connect(analyser); |
| | updateWidgetValueInRealTime(); |
| | }).catch(error => { |
| | console.error('Access to microphone was denied or an error occurred:', error); |
| | }); |
| | }; |
| | |
| | |
| | const stopMicrophoneCapture = () => { |
| | if (animationFrameId) { |
| | cancelAnimationFrame(animationFrameId); |
| | } |
| | if (microphoneStream) { |
| | microphoneStream.getTracks().forEach(track => track.stop()); |
| | } |
| | if (audioContext) { |
| | audioContext.close(); |
| | |
| | audioContext = null; |
| | } |
| | }; |
| | |
| | |
| | this.addWidget("button", "Start mic capture", null, startMicrophoneCapture); |
| | |
| | |
| | this.addWidget("button", "Stop mic capture", null, stopMicrophoneCapture); |
| | }; |
| | break; |
| | |
| | } |
| | |
| | }, |
| | async setup() { |
| | |
| | const originalComputeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes; |
| | LGraphCanvas.prototype.computeVisibleNodes = function () { |
| | const visibleNodesSet = new Set(originalComputeVisibleNodes.apply(this, arguments)); |
| | for (const node of this.graph._nodes) { |
| | if ((node.type === "SetNode" || node.type === "GetNode") && node.drawConnection) { |
| | visibleNodesSet.add(node); |
| | } |
| | } |
| | return Array.from(visibleNodesSet); |
| | }; |
| |
|
| | } |
| | }); |