|
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": |
|
case "CrossFadeImagesMulti": |
|
case "TransitionImagesMulti": |
|
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); |
|
}; |
|
|
|
} |
|
}); |