Skip to content

Commit

Permalink
new edge mode for GLSL shaders (clamp, wrap, mirror)
Browse files Browse the repository at this point in the history
comparison node value conversion fixed
cleaned up midi filtering
  • Loading branch information
Amorano committed Sep 8, 2024
1 parent 54d61c3 commit 4c9a28f
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 171 deletions.
2 changes: 2 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ class Lexicon(metaclass=LexiconMeta):
DPI = 'DPI', "Use DPI mode from OS"
EASE = 'EASE', "Easing function"
EDGE = 'EDGE', "Clip or Wrap the Canvas Edge"
EDGE_X = 'EDGE_X', "Clip or Wrap the Canvas Edge"
EDGE_Y = 'EDGE_Y', "Clip or Wrap the Canvas Edge"
END = 'END', "End of the range"
FALSE = '🇫', "False"
FILEN = '💾', "File Name"
Expand Down
25 changes: 13 additions & 12 deletions core/calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ def INPUT_TYPES(cls) -> dict:
Lexicon.COMP_B: (JOV_TYPE_ANY, {"default": 0}),
Lexicon.COMPARE: (EnumComparison._member_names_, {"default": EnumComparison.EQUAL.name}),
Lexicon.FLIP: ("BOOLEAN", {"default": False}),
Lexicon.INVERT: ("BOOLEAN", {"default": False}),
Lexicon.INVERT: ("BOOLEAN", {"default": False, "tooltips":"reverse the successful and failure inputs"}),
},
"outputs": {
0: (Lexicon.TRIGGER, {"tooltips":f"Outputs the input at {Lexicon.IN_A} or {Lexicon.IN_B} depending on which evaluated `TRUE`"}),
Expand All @@ -457,8 +457,8 @@ def INPUT_TYPES(cls) -> dict:
return Lexicon._parse(d, cls)

def run(self, **kw) -> Tuple[Any, Any]:
A = parse_param(kw, Lexicon.IN_A, EnumConvertType.VEC4, 0)
B = parse_param(kw, Lexicon.IN_B, EnumConvertType.VEC4, 0)
A = parse_param(kw, Lexicon.IN_A, EnumConvertType.ANY, 0)
B = parse_param(kw, Lexicon.IN_B, EnumConvertType.ANY, 0)
good = parse_param(kw, Lexicon.COMP_A, EnumConvertType.ANY, 0)
fail = parse_param(kw, Lexicon.COMP_B, EnumConvertType.ANY, 0)
op = parse_param(kw, Lexicon.COMPARE, EnumConvertType.STRING, EnumComparison.EQUAL.name)
Expand All @@ -469,22 +469,23 @@ def run(self, **kw) -> Tuple[Any, Any]:
vals = []
results = []
for idx, (A, B, good, fail, op, flip, invert) in enumerate(params):
if not isinstance(A, (list, set, tuple)):
if not isinstance(A, (list,)):
A = [A]
if not isinstance(B, (list, set, tuple)):
if not isinstance(B, (list,)):
B = [B]
size = min(4, max(len(A), len(B))) - 1
typ = [EnumConvertType.FLOAT, EnumConvertType.VEC2, EnumConvertType.VEC3, EnumConvertType.VEC4][size]
val_a = parse_value(A, typ, [A[-1]] * size)
val_b = parse_value(B, typ, [B[-1]] * size)
if flip:
val_a, val_b = val_b, val_a

if not isinstance(val_a, (list, tuple)):
val_a = parse_value(A, typ, [A[-1]] * size)
if not isinstance(val_a, (list,)):
val_a = [val_a]
if not isinstance(val_b, (list, tuple)):

val_b = parse_value(B, typ, [B[-1]] * size)
if not isinstance(val_b, (list,)):
val_b = [val_b]

if flip:
val_a, val_b = val_b, val_a
op = EnumComparison[op]
match op:
case EnumComparison.EQUAL:
Expand Down Expand Up @@ -539,7 +540,7 @@ def run(self, **kw) -> Tuple[Any, Any]:
outs = outs[0].unsqueeze(0)
else:
outs = list(outs)
return outs, list(vals)
return outs, vals,

class DelayNode(JOVBaseNode):
NAME = "DELAY (JOV) ✋🏽"
Expand Down
31 changes: 15 additions & 16 deletions core/create_glsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import os
import sys
from enum import Enum
from pathlib import Path
from typing import Any, Tuple

Expand All @@ -19,15 +18,17 @@
pass
from comfy.utils import ProgressBar

from Jovimetrix import ROOT, JOV_TYPE_ANY, Lexicon, JOVImageNode, comfy_message, \
deep_merge
from Jovimetrix import ROOT, JOV_TYPE_ANY, Lexicon, JOVImageNode, \
comfy_message, deep_merge

from Jovimetrix.sup.util import EnumConvertType, load_file, parse_param, parse_value
from Jovimetrix.sup.util import EnumConvertType, load_file, parse_param, \
parse_value

from Jovimetrix.sup.image import MIN_IMAGE_SIZE, EnumInterpolation, EnumScaleMode, \
cv2tensor_full, image_convert, image_scalefit, tensor2cv
from Jovimetrix.sup.image import MIN_IMAGE_SIZE, EnumInterpolation, \
EnumScaleMode, cv2tensor_full, image_convert, image_scalefit, tensor2cv

from Jovimetrix.sup.shader import PTYPE, CompileException, GLSLShader, shader_meta
from Jovimetrix.sup.shader import PTYPE, CompileException, EnumEdgeGLSL, \
GLSLShader, shader_meta

# =============================================================================

Expand All @@ -52,12 +53,6 @@

JOV_CATEGORY = "CREATE"

class EnumEdgeGLSL(Enum):
CLIP = 1
WRAP = 2
WRAPX = 3
WRAPY = 4

# =============================================================================

try:
Expand Down Expand Up @@ -107,7 +102,8 @@ def INPUT_TYPES(cls) -> dict:
Lexicon.WH: ("VEC2INT", {"default": (512, 512), "mij":MIN_IMAGE_SIZE, "label": [Lexicon.W, Lexicon.H]}),
Lexicon.SAMPLE: (EnumInterpolation._member_names_, {"default": EnumInterpolation.LANCZOS4.name}),
Lexicon.MATTE: ("VEC4INT", {"default": (0, 0, 0, 255), "rgb": True}),
Lexicon.EDGE: (EnumInterpolation._member_names_, {"default": EnumInterpolation.LANCZOS4.name}),
Lexicon.EDGE_X: (EnumEdgeGLSL._member_names_, {"default": EnumEdgeGLSL.CLAMP.name}),
Lexicon.EDGE_Y: (EnumEdgeGLSL._member_names_, {"default": EnumEdgeGLSL.CLAMP.name}),
}
})
return Lexicon._parse(d, cls)
Expand All @@ -128,6 +124,9 @@ def run(self, ident, **kw) -> tuple[torch.Tensor]:
sample = parse_param(kw, Lexicon.SAMPLE, EnumConvertType.STRING, EnumInterpolation.LANCZOS4.name)[0]
sample = EnumInterpolation[sample]
matte = parse_param(kw, Lexicon.MATTE, EnumConvertType.VEC4INT, [(0, 0, 0, 255)], 0, 255)[0]
edge_x = parse_param(kw, Lexicon.EDGE_X, EnumConvertType.STRING, EnumEdgeGLSL.CLAMP.name)[0]
edge_y = parse_param(kw, Lexicon.EDGE_Y, EnumConvertType.STRING, EnumEdgeGLSL.CLAMP.name)[0]
edge = (edge_x, edge_y)

try:
self.__glsl.vertex = kw.pop(Lexicon.PROG_VERT, self.VERTEX)
Expand All @@ -139,7 +138,7 @@ def run(self, ident, **kw) -> tuple[torch.Tensor]:
return

variables = kw.copy()
for p in [Lexicon.MODE, Lexicon.WH, Lexicon.SAMPLE, Lexicon.MATTE, Lexicon.BATCH, Lexicon.TIME, Lexicon.FPS]:
for p in [Lexicon.MODE, Lexicon.WH, Lexicon.SAMPLE, Lexicon.MATTE, Lexicon.BATCH, Lexicon.TIME, Lexicon.FPS, Lexicon.EDGE]:
variables.pop(p, None)

self.__glsl.fps = parse_param(kw, Lexicon.FPS, EnumConvertType.INT, 24, 1, 120)[0]
Expand Down Expand Up @@ -173,7 +172,7 @@ def run(self, ident, **kw) -> tuple[torch.Tensor]:

self.__glsl.size = (w, h)

img = self.__glsl.render(self.__delta, **vars)
img = self.__glsl.render(self.__delta, edge, **vars)
if mode != EnumScaleMode.MATTE:
img = image_scalefit(img, w, h, mode, sample)
img = cv2tensor_full(img, matte)
Expand Down
187 changes: 84 additions & 103 deletions core/device_midi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@
type 2 (asynchronous): each track is independent of the others
"""

from typing import Tuple
from typing import Any, Tuple
from math import isclose
from queue import Queue

from loguru import logger

from comfy.utils import ProgressBar

from Jovimetrix import deep_merge, JOVBaseNode, Lexicon
from Jovimetrix.sup.util import parse_param, zip_longest_fill, EnumConvertType
from Jovimetrix.sup.midi import midi_device_names, \
MIDIMessage, MIDINoteOnFilter, MIDIServerThread
from Jovimetrix import JOV_TYPE_ANY, JOVBaseNode, Lexicon, deep_merge

from Jovimetrix.sup.util import EnumConvertType, parse_param, zip_longest_fill

from Jovimetrix.sup.midi import MIDIMessage, MIDINoteOnFilter, MIDIServerThread,\
midi_device_names

# =============================================================================

Expand Down Expand Up @@ -122,76 +124,10 @@ def run(self, **kw) -> Tuple[MIDIMessage, bool, int, int, int, int, float]:
msg = MIDIMessage(self.__note_on, self.__channel, self.__control, self.__note, self.__value)
return msg, self.__note_on, self.__channel, self.__control, self.__note, self.__value, normalize,

class MIDIFilterEZNode(JOVBaseNode):
NAME = "MIDI FILTER EZ (JOV) ❇️"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ('JMIDIMSG', 'BOOLEAN',)
RETURN_NAMES = (Lexicon.MIDI, Lexicon.TRIGGER,)
SORT = 25
DESCRIPTION = """
Filter MIDI messages based on various criteria, including MIDI mode (such as note on or note off), MIDI channel, control number, note number, value, and normalized value. This node is useful for processing MIDI input and selectively passing through only the desired messages. It helps simplify MIDI data handling by allowing you to focus on specific types of MIDI events.
"""

@classmethod
def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
Lexicon.MIDI: ('JMIDIMSG', {"default": None}),
Lexicon.MODE: (MIDINoteOnFilter._member_names_, {"default": MIDINoteOnFilter.IGNORE.name}),
Lexicon.CHANNEL: ("INT", {"default": -1, "mij": -1, "maj": 127}),
Lexicon.CONTROL: ("INT", {"default": -1, "mij": -1, "maj": 127}),
Lexicon.NOTE: ("INT", {"default": -1, "mij": -1, "maj": 127}),
Lexicon.VALUE: ("INT", {"default": -1, "mij": -1, "maj": 127}),
Lexicon.NORMALIZE: ("FLOAT", {"default": -1, "mij": -1, "maj": 1, "step": 0.01})
}
})
return Lexicon._parse(d, cls)

def run(self, **kw) -> Tuple[MIDIMessage, bool]:

message: MIDIMessage = parse_param(kw, Lexicon.MIDI, EnumConvertType.ANY, None)
mode = parse_param(kw, Lexicon.MODE, EnumConvertType.STRING, MIDINoteOnFilter.IGNORE.name)
chan = parse_param(kw, Lexicon.CHANNEL, EnumConvertType.INT, -1)
ctrl = parse_param(kw, Lexicon.CONTROL, EnumConvertType.INT, -1)
note = parse_param(kw, Lexicon.NOTE, EnumConvertType.INT, -1)
value = parse_param(kw, Lexicon.VALUE, EnumConvertType.INT, -1)
normal = parse_param(kw, Lexicon.NORMALIZE, EnumConvertType.FLOAT, -1)
params = list(zip_longest_fill(message, mode, chan, ctrl, note, value, normal))
ret = []
pbar = ProgressBar(len(params))
for idx, (message, mode, chan, ctrl, note, value, normal) in enumerate(params):
mode = MIDINoteOnFilter[mode]
if mode != MIDINoteOnFilter.IGNORE:
if mode == MIDINoteOnFilter.NOTE_ON and message.note_on == False:
ret.append((message, False, ))
continue
elif mode == MIDINoteOnFilter.NOTE_OFF and message.note_on == False:
ret.append((message, False, ))
continue
if chan != -1 and chan != message.channel:
ret.append((message, False, ))
continue
if ctrl != -1 and ctrl != message.control:
ret.append((message, False, ))
continue
if note != -1 and note != message.note:
ret.append((message, False, ))
continue
if value != -1 and value != message.value:
ret.append((message, False, ))
continue
if normal > 0 and not isclose(message.normal):
ret.append((message, False, ))
continue
ret.append((message, True, ))
pbar.update_absolute(idx)
return [list(x) for x in (zip(*ret))]

class MIDIFilterNode(JOVBaseNode):
NAME = "MIDI FILTER (JOV) ✳️"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ('JMIDIMSG', 'BOOLEAN', )
RETURN_TYPES = ("JMIDIMSG", "BOOLEAN", )
RETURN_NAMES = (Lexicon.MIDI, Lexicon.TRIGGER,)
SORT = 20
EPSILON = 1e-6
Expand All @@ -210,7 +146,7 @@ def INPUT_TYPES(cls) -> dict:
Lexicon.CONTROL: ("STRING", {"default": ""}),
Lexicon.NOTE: ("STRING", {"default": ""}),
Lexicon.VALUE: ("STRING", {"default": ""}),
Lexicon.NORMALIZE: ("STRING", {"default": ""})
Lexicon.NORMALIZE: ("STRING", {"default": ""}),
}
})
return Lexicon._parse(d, cls)
Expand Down Expand Up @@ -246,33 +182,78 @@ def __filter(self, data:int, value:str) -> bool:
return False

def run(self, **kw) -> Tuple[bool]:
message: MIDIMessage = parse_param(kw, Lexicon.MIDI, EnumConvertType.ANY, None)
note_on = parse_param(kw, Lexicon.ON, EnumConvertType.STRING, MIDINoteOnFilter.IGNORE.name)
chan = parse_param(kw, Lexicon.CHANNEL, EnumConvertType.STRING, "")
ctrl = parse_param(kw, Lexicon.CONTROL, EnumConvertType.STRING, "")
note = parse_param(kw, Lexicon.NOTE, EnumConvertType.STRING, "")
value = parse_param(kw, Lexicon.VALUE, EnumConvertType.STRING, "")
normal = parse_param(kw, Lexicon.NORMALIZE, EnumConvertType.STRING, "")
params = list(zip_longest_fill(message, note_on, chan, ctrl, note, value, normal))
results = []
pbar = ProgressBar(len(params))
for idx, (message, note_on, chan, ctrl, note, value, normal) in enumerate(params):
note_on = MIDINoteOnFilter[note_on]
if note_on != MIDINoteOnFilter.IGNORE:
if note_on == "TRUE" and message.note_on != True:
results.append((message, False, ))
if note_on == "FALSE" and message.note_on != False:
results.append((message, False, ))
elif self.__filter(message.channel, chan) == False:
results.append((message, False, ))
elif self.__filter(message.control, ctrl) == False:
results.append((message, False, ))
elif self.__filter(message.note, note) == False:
results.append((message, False, ))
elif self.__filter(message.value, value) == False:
results.append((message, False, ))
elif self.__filter(message.normal, normal) == False:
results.append((message, False, ))
results.append((message, True, ))
pbar.update_absolute(idx)
return [list(x) for x in (zip(*results))]
message: MIDIMessage = kw.get(Lexicon.MIDI, None)
note_on: str = parse_param(kw, Lexicon.ON, EnumConvertType.STRING, MIDINoteOnFilter.IGNORE.name)[0]
chan: str = parse_param(kw, Lexicon.CHANNEL, EnumConvertType.STRING, "")[0]
ctrl: str = parse_param(kw, Lexicon.CONTROL, EnumConvertType.STRING, "")[0]
note: str = parse_param(kw, Lexicon.NOTE, EnumConvertType.STRING, "")[0]
value: str = parse_param(kw, Lexicon.VALUE, EnumConvertType.STRING, "")[0]
normal: str = parse_param(kw, Lexicon.NORMALIZE, EnumConvertType.STRING, "")[0]

note_on = MIDINoteOnFilter[note_on]
if note_on != MIDINoteOnFilter.IGNORE:
if note_on == MIDINoteOnFilter.NOTE_ON and message.note_on != True:
return message, False,
if note_on == MIDINoteOnFilter.NOTE_OFF and message.note_on != False:
return message, False,
elif self.__filter(message.channel, chan) == False:
return message, False,
elif self.__filter(message.control, ctrl) == False:
return message, False,
elif self.__filter(message.note, note) == False:
return message, False,
elif self.__filter(message.value, value) == False:
return message, False,
elif self.__filter(message.normal, normal) == False:
return message, False,
return message, True,

class MIDIFilterEZNode(JOVBaseNode):
NAME = "MIDI FILTER EZ (JOV) ❇️"
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/{JOV_CATEGORY}"
RETURN_TYPES = ("JMIDIMSG", "BOOLEAN", )
RETURN_NAMES = (Lexicon.MIDI, Lexicon.TRIGGER,)
SORT = 25
DESCRIPTION = """
Filter MIDI messages based on various criteria, including MIDI mode (such as note on or note off), MIDI channel, control number, note number, value, and normalized value. This node is useful for processing MIDI input and selectively passing through only the desired messages. It helps simplify MIDI data handling by allowing you to focus on specific types of MIDI events.
"""

@classmethod
def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
Lexicon.MIDI: ('JMIDIMSG', {"default": None}),
Lexicon.MODE: (MIDINoteOnFilter._member_names_, {"default": MIDINoteOnFilter.IGNORE.name}),
Lexicon.CHANNEL: ("INT", {"default": -1, "mij": -1, "maj": 127}),
Lexicon.CONTROL: ("INT", {"default": -1, "mij": -1, "maj": 127}),
Lexicon.NOTE: ("INT", {"default": -1, "mij": -1, "maj": 127}),
Lexicon.VALUE: ("INT", {"default": -1, "mij": -1, "maj": 127}),
}
})
return Lexicon._parse(d, cls)

def run(self, **kw) -> Tuple[MIDIMessage, bool]:

message: MIDIMessage = parse_param(kw, Lexicon.MIDI, EnumConvertType.ANY, None)[0]
note_on = parse_param(kw, Lexicon.MODE, EnumConvertType.STRING, MIDINoteOnFilter.IGNORE.name)[0]
chan = parse_param(kw, Lexicon.CHANNEL, EnumConvertType.INT, -1)[0]
ctrl = parse_param(kw, Lexicon.CONTROL, EnumConvertType.INT, -1)[0]
note = parse_param(kw, Lexicon.NOTE, EnumConvertType.INT, -1)[0]
value = parse_param(kw, Lexicon.VALUE, EnumConvertType.INT, -1)[0]

note_on = MIDINoteOnFilter[note_on]
if note_on != MIDINoteOnFilter.IGNORE:
if note_on == MIDINoteOnFilter.NOTE_ON and message.note_on != True:
return message, False,
if note_on == MIDINoteOnFilter.NOTE_OFF and message.note_on != False:
return message, False,
elif chan > -1 and message.channel != chan:
return message, False,
elif ctrl > -1 and message.control != ctrl:
return message, False,
elif note > -1 and message.note != note:
return message, False,
elif value > -1 and message.value != value:
return message, False,
return message, True,
2 changes: 1 addition & 1 deletion node_list.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
"GLSL NORMAL (JOV) \ud83e\uddd9\ud83c\udffd": "Convert input into a Normal map",
"GLSL NORMAL BLEND (JOV) \ud83e\uddd9\ud83c\udffd": "Blend two Normal maps",
"GLSL POSTERIZE (JOV) \ud83e\uddd9\ud83c\udffd": "Reduce the pixel color data range",
"GLSL REPATILE (JOV) \ud83e\uddd9\ud83c\udffd": "REPATILE",
"GLSL RGB-2-HSV (JOV) \ud83e\uddd9\ud83c\udffd": "Convert RGB(A) input into HSV color space",
"GLSL RGB-2-LAB (JOV) \ud83e\uddd9\ud83c\udffd": "Convert RGB(A) image into LAB color space",
"GLSL RGB-2-XYZ (JOV) \ud83e\uddd9\ud83c\udffd": "Convert RGB(A) input into XYZ color space",
"GLSL TRANSFORM (JOV) \ud83e\uddd9\ud83c\udffd": "TRANSFORM",
"GLSL XYZ-2-LAB (JOV) \ud83e\uddd9\ud83c\udffd": "Convert XYZ(W) image into LAB color space",
"GLSL XYZ-2-RGB (JOV) \ud83e\uddd9\ud83c\udffd": "Convert XYZ(W) image into RGB color space",
"GRADIENT MAP (JOV) \ud83c\uddf2\ud83c\uddfa": "Remaps an input image using a gradient lookup table (LUT)",
Expand Down
Loading

0 comments on commit 4c9a28f

Please sign in to comment.