import Error, Constant, PureOp, Op, template_subst, Input, T, Array, Struct, sig, evt, const, any from require 'alv.base' import Message, Bundle, Timetag, add_item from require 'alv-lib._osc' import dns, udp from require 'socket' fmttag = (tag) -> tag\ident!\gsub '[%.:%-/\\]', '_' tryprefix = (prefix, filename) -> return filename unless prefix and filename\match '^%./' prefix .. filename GLSL_TYPES = num: 'float' bool: 'bool' for i=2,4 GLSL_TYPES["num[#{i}]"] = "vec#{i}" GLSL_TYPES["bool[#{i}]"] = "bvec#{i}" for j=2,4 GLSL_TYPES["num[#{j}][#{i}]"] = "mat#{i}x#{j}" GLSL_SAMPLER_TYPES = "1D": 'sampler1D' "2D": 'sampler2D' "3D": 'sampler3D' "CUBE_MAP": 'samplerCube' "RECTANGLE": 'sampler2DRect' "1D_ARRAY": 'sampler1DArray' "2D_ARRAY": 'sampler2DArray' "CUBE_MAP_ARRAY": 'samplerCubeArray' "BUFFER": 'samplerBuffer' Uniform = Struct type: T.str, val: T.str get_typestr = (type) -> if typestr = GLSL_TYPES[tostring type] return typestr if type.__class == Array if typestr = GLSL_TYPES[tostring type.type] return "#{typestr}[#{type.size}]" error Error 'type', "can't wrap #{type} in uniform" uniform = Constant.meta meta: name: 'uniform' summary: "override uniform binding type." examples: { '(uniform [glsl-type] value)' } description: "Label an alive value with a GLSL type: `(uniform 'sampler2D' 'sampler-name')` is equivalent to `{'type' 'sampler2D' 'val' 'sampler-name'}`. When absent, `glsl-type` is inferred based on the type of `value`: - `num`, `bool`: `uniform float/bool` - `num[I]`, `bool[I]`: `uniform vecI/bvecI` - `num[I][J]`: `uniform matIxJ`" value: class extends PureOp pattern: -sig.str + sig! type: (inputs) => Struct type: T.str, val: inputs[2]\type! tick: => { type, val } = @unwrap_all! type or= get_typestr @inputs[2]\type! @out\set :type, :val offset= Constant.meta meta: name: 'offset' summary: "get the playback offset of a buffered-stream-source." examples: { '(offset [glsl-type] source)' } description: "GLSL type: float/double/int/uint" value: class extends PureOp pattern: -sig.str + sig! type: => Uniform tick: => { type, source } = @unwrap_all! type or= "float" name = source.val @out\set :type, val: "/source/#{name}/offset" video_source = Constant.meta meta: name: 'video-source' summary: "load a video texture source." examples: { '(video-source [socket] type filename [format] [args])' } description: "Creates a texture source from a video or image file. - `type` can be one of: - `2D`: `sampler2D` (loads first frame only) - `3D` or `2D_ARRAY`: `sampler3D`/`sampler2DArray` (loads whole video) - `filename` is the video file to load. If it starts with `./` it will be resolved relative to the current alv module. - `format` optionally specifies the ffmpeg/avformat input format to use. - `args` is a struct of optional arguments for the ffmpeg/avformat input format." value: class extends Op pattern = -sig['osc/out'] + const.str + const.str + -const.str + -const! setup: (inputs, scope) => { socket, type, filename, format, args } = pattern\match inputs @prefix = COPILOT.active_module.file\match'(.*/)[^/]*$' super socket: Input.hot socket or scope\get '*oscout*' type: Input.cold type filename: Input.cold filename format: format and Input.cold format args: args and Input.cold args samplertype = assert GLSL_SAMPLER_TYPES[@inputs.type!] @setup_out '=', Uniform, type: samplertype, val: "alv-#{fmttag @tag}" tick: => { :socket, :type, :filename, :format, :args } = @unwrap_all! with @inputs.socket! filename = tryprefix @prefix, filename msg = Message.new{ address: "/source/alv-#{fmttag @tag}/video", types: "ss", "TEXTURE_#{type}", filename } msg\add 's', format if format add_item msg, @inputs.args\type!, args if args \send Message.pack msg.content -- destroy: => -- name = @inputs.name! -- with @inputs.socket! -- \send Message.pack { address: "/source/#{name}/destroy", types: "" } stream_source = Constant.meta meta: name: 'stream-source' summary: "load a stream texture source." examples: { '(stream-source [socket] type uri [format] [args])' } description: "Creates a realtime stream from a capture source. - `type` must be `2D`. - `uri` is the ffmpeg URI to load. If it starts with `./` it will be resolved relative to the current alv module. - `format` optionally specifies the ffmpeg/avformat input format to use. - `args` is a struct of optional arguments for the ffmpeg/avformat input format. (stream-source 'webcam' '2D' '/dev/video0') (stream-source 'testpt' '2D' 'testsrc=size=1280x720:rate=30' 'lavfi')" value: class extends Op pattern = -sig['osc/out'] + const.str + const.str + -const.str + -const! setup: (inputs, scope) => { socket, type, filename, format, args } = pattern\match inputs @prefix = COPILOT.active_module.file\match'(.*/)[^/]*$' super socket: Input.hot socket or scope\get '*oscout*' type: Input.cold type filename: Input.cold filename format: format and Input.cold format args: args and Input.cold args samplertype = assert GLSL_SAMPLER_TYPES[@inputs.type!] @setup_out '=', Uniform, type: samplertype, val: "alv-#{fmttag @tag}" tick: => { :socket, :name, :type, :filename, :format, :args } = @unwrap_all! with @inputs.socket! filename = tryprefix @prefix, filename msg = Message.new{ address: "/source/alv-#{fmttag @tag}/stream", types: "ss", "TEXTURE_#{type}", filename } msg\add 's', format if format add_item msg, @inputs.args\type!, args if args \send Message.pack msg.content buffered_stream_source = Constant.meta meta: name: 'buffered-stream-source' summary: "load a stream as a 3d texture source." examples: { '(buffered-stream-source [socket] type depth uri [format] [args])' } description: "Creates a circular buffer 3d texture from a capture source. - `type` must be `3D` or `2D_ARRAY`. - `depth` is the number of frames to keep in the buffer. - `uri` is the ffmpeg URI to load. If it starts with `./` it will be resolved relative to the current alv module. - `format` optionally specifies the ffmpeg/avformat input format to use. - `args` is a struct of optional arguments for the ffmpeg/avformat input format. (stream-source 'webcam' '3D' 64 '/dev/video0')" value: class extends Op pattern = -sig['osc/out'] + const.str + const.num + const.str + -const.str + -const! setup: (inputs, scope) => { socket, type, depth, filename, format, args } = pattern\match inputs @prefix = COPILOT.active_module.file\match'(.*/)[^/]*$' super socket: Input.hot socket or scope\get '*oscout*' type: Input.cold type depth: Input.cold depth filename: Input.cold filename format: format and Input.cold format args: args and Input.cold args samplertype = assert GLSL_SAMPLER_TYPES[@inputs.type!] @setup_out '=', Uniform, type: samplertype, val: "alv-#{fmttag @tag}" tick: => { :socket, :name, :type, :depth, :filename, :format, :args } = @unwrap_all! with @inputs.socket! filename = tryprefix @prefix, filename msg = Message.new{ address: "/source/alv-#{fmttag @tag}/buffered-stream", types: "sis", "TEXTURE_#{type}", depth, filename } msg\add 's', format if format add_item msg, @inputs.args\type!, args if args \send Message.pack msg.content tsv_source = Constant.meta meta: name: 'tsv-source' summary: "load a texture-share-vk texture source." examples: { '(tsv-source [socket] name [type])' } description: "Creates a texture source from a texture-share-vk texture. - `name` is the tsv name of this texture source. - `type` defaults to `2D`." value: class extends Op pattern = -sig['osc/out'] + const.str + -const.str setup: (inputs, scope) => { socket, name, type } = pattern\match inputs super socket: Input.hot socket or scope\get '*oscout*' name: Input.hot name type: Input.hot type or Constant.str "2D" samplertype = assert GLSL_SAMPLER_TYPES[@inputs.type!] @setup_out '=', Uniform, type: samplertype, val: "alv-#{fmttag @tag}" tick: => { :socket, :name, :type } = @unwrap_all! with @inputs.socket! \send Message.pack { address: "/source/alv-#{fmttag @tag}/tsv", types: "ss", "TEXTURE_#{type}", name } freeze = Constant.meta meta: name: 'freeze' summary: "freeze a texture source." examples: { '(freeze [socket] source freeze?)' } value: class extends Op pattern = -sig['osc/out'] + const[Uniform] + any.bool setup: (inputs, scope) => { socket, source, freeze } = pattern\match inputs super socket: Input.hot socket or scope\get '*oscout*' source: Input.cold source freeze: Input.hot freeze tick: => { :socket, :source, :freeze } = @unwrap_all! with @inputs.socket! msg = Message.new{ address: "/source/#{source.val}/freeze", types: "" } add_item msg, @inputs.freeze\type!, freeze \send Message.pack msg.content step_ = Constant.meta meta: name: 'step!' summary: "step a frozen texture source." examples: { '(step! [socket] source [trig!])' } value: class extends Op pattern = -sig['osc/out'] + any[Uniform] + -evt.bang setup: (inputs, scope) => { socket, source, trig } = pattern\match inputs super socket: Input.hot socket or scope\get '*oscout*' source: Input.hot source trig: trig and Input.hot trig tick: => { :socket, :source, :trig } = @unwrap_all! with @inputs.socket! \send Message.pack { address: "/source/#{source.val}/step", types: "I" } shader_ = Constant.meta meta: name: 'shader' summary: "compile a GLSL shader with uniforms." examples: { '$shader"code…"' } description: "A template string formatter for [glsl-view/draw][] that returns a shader struct~. - `code…` is a template string that can contain `~-stream` template substitutions that are used as follows: - `str`: substituted directly as GLSL - another shader struct~: substituted as GLSL, with uniforms lifted - `{type: str val: …}`: reference to uniform of GLSL-type `type` - other values are implicitly converted as by [glsl-view/uniform][]" value: class extends PureOp pattern: const! + sig!^0 name: (i) => "alv_u#{fmttag @tag}_#{i}" type: (args) => uniforms = {} for i, val in ipairs args[2] type = val\type! if type == T.str continue else if type.__class == Struct and type.types['shader'] for name, typ in pairs type.types['uniforms'].types uniforms[name] = typ else if type.__class == Struct assert type.types['type'] == T.str, Error 'type', "struct substituton needs type key" else type = Struct type: T.str, val: type uniforms[@name i] = type Struct shader: T.str, uniforms: Struct uniforms tick: => uniforms = {} pieces = for i, input in ipairs @inputs[2] name, input = (@name i), input val, type = input!, input\type! if type == T.str val elseif type.__class == Struct and type.types['shader'] for k, v in pairs val.uniforms uniforms[k] = v val.shader else if type.__class != Struct val = :val, type: get_typestr type uniforms[name] = val name shader = template_subst @inputs[1]!, pieces @out\set :shader, :uniforms draw = Constant.meta meta: name: 'draw' summary: "draw a GLSL shader via OSC." examples: { '(draw [socket] shader)' } description: "Syncs code and parameters with glsl-view via OSC. - `socket` must be an `osc/out~` or read from `*oscout*` - `shader` is a struct with - `shader` (`str`): GLSL source - `uniforms`: struct mapping uniform names to values" value: class extends Op pattern = -sig['osc/out'] + any! setup: (inputs, scope) => { socket, shader } = pattern\match inputs super socket: Input.hot socket or scope\get '*oscout*' shader: Input.hot shader tick: => { :socket, :shader } = @unwrap_all! if not shader return with Bundle.new Timetag.new! chunks = { '#version 330 core\n' } for key, val in pairs shader.uniforms typ, arrsuff = val.type\match "^(%w+)(%[%d+%])$" table.insert chunks, if typ and arrsuff print "typ and arrsuf", typ, arrsuff "uniform #{typ} #{key}#{arrsuff};" else "uniform #{val.type} #{key};" table.insert chunks, shader.shader \add content: { address: '/shader', types: 's', table.concat chunks, '\n' } types = @inputs.shader\type!\get 'uniforms' for key, val in pairs shader.uniforms type = types\get(key)\get 'val' msg = Message.new "/uniform/#{key}" add_item msg, type, val.val \add msg socket\send Bundle.pack .content Constant.meta meta: name: 'glsl-view' summary: "glsl-view integration via OSC." value: :uniform 'video-source': video_source 'stream-source': stream_source 'buffered-stream-source': buffered_stream_source 'tsv-source': tsv_source :offset :freeze, 'step!': step_ shader: shader_ :draw