├── .gitignore ├── .vscode └── settings.json ├── Dolby CP Emulator ├── CP650 Emulator.quc ├── CP750 Emulator.quc └── CP850 Emulator.quc ├── .gitmodules ├── README.md ├── Developer ├── Modules │ ├── strict.lua │ ├── dolbyfader.lua │ ├── dolbysweep.lua │ ├── qknob.lua │ ├── cpseries.lua │ └── cpseries_class.lua └── plugins │ ├── reference.lua │ ├── DolbyFader V1.1.qplug │ ├── Dolby Sweep V1.1.qplug │ ├── Dolby CPSeries Control V2.2.qplug │ └── MultiFlip-Flop V1.1.qplug ├── MultiFlip-Flop.qplug ├── DolbyFader.qplug ├── Dolby Sweep V1.04.qplug └── Dolby CPSeries Control V2.2.qplugx /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.qplug": "lua" 4 | } 5 | } -------------------------------------------------------------------------------- /Dolby CP Emulator/CP650 Emulator.quc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaumeAP/qsys-plugins/HEAD/Dolby CP Emulator/CP650 Emulator.quc -------------------------------------------------------------------------------- /Dolby CP Emulator/CP750 Emulator.quc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaumeAP/qsys-plugins/HEAD/Dolby CP Emulator/CP750 Emulator.quc -------------------------------------------------------------------------------- /Dolby CP Emulator/CP850 Emulator.quc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaumeAP/qsys-plugins/HEAD/Dolby CP Emulator/CP850 Emulator.quc -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Developer/Modules/class"] 2 | path = Developer/Modules/class 3 | url = https://github.com/jonstoler/class.lua 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Plugins for QSYS 2 | ================ 3 | ### Dolby Fader 4 | Command component that emulates Dolby CP Proessors fader (0.0 to 10.0) 5 | ### Dolby CPSeries Control 6 | Component that controls All CP Dolby Processors from CP650 to CP950 7 | ### Dolby Sweet 8 | Dolby Sweet Tone Generator 9 | ### Multi-Flipflop 10 | Multiple FlipFlop in one component 11 | 12 | -------------------------------------------------------------------------------- /Developer/Modules/strict.lua: -------------------------------------------------------------------------------- 1 | -------- STRICT ----------------------- 2 | -- Verify strict variables 3 | -- Error if variable is not declared 4 | --------------------------------------- 5 | 6 | do 7 | 8 | local mt = getmetatable(_G) 9 | 10 | if mt == nil then 11 | mt = {} 12 | setmetatable(_G, mt) 13 | end 14 | mt.__declared = {} 15 | 16 | function Global(...) 17 | for _, key in ipairs{...} do 18 | mt.__declared[key] = true 19 | end 20 | end 21 | 22 | mt.__newindex = function (table, key, value) 23 | if not mt.__declared[key] then 24 | if debug.getinfo(2, "S").what ~= "C" then 25 | error("assign to undeclared variable '"..key.."'", 2) 26 | end 27 | mt.__declared[key] = true 28 | end 29 | rawset(table, key, value) 30 | end 31 | 32 | mt.__index = function (table, key) 33 | if not mt.__declared[key] and debug.getinfo(2, "S").what ~= "C" then 34 | error("variable '"..key.."' is not declared", 2) 35 | end 36 | return rawget(table, key) 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /Developer/Modules/dolbyfader.lua: -------------------------------------------------------------------------------- 1 | --## Code for Dolby Fader 2 | 3 | require("qknob") 4 | 5 | DKNob = QKnob:new('level',0,10,1) 6 | 7 | local function convertToDb(val) 8 | if val <= 4 then return val*20-90 9 | else return (val-7)*10/3 end 10 | end 11 | 12 | local function convertToDolby(dB) 13 | if dB <= -10 then return (dB+90)/20 14 | else return (dB*3/10)+7 end 15 | end 16 | 17 | Step.value.EventHandler = function(ctrl) 18 | DKNob.Position = Step.value.Value / 100 19 | DKNob.EventHandler(Step) 20 | end 21 | 22 | DKNob.EventHandler = function(ctrl) 23 | if ctrl ~= Controls.gain then 24 | Controls.gain.Value = convertToDb( DKNob.Value ) 25 | end 26 | Step.value.Value = DKNob.Position * 100 27 | if DolbyFaderEventHandler then 28 | DolbyFaderEventHandler(ctrl) 29 | end 30 | end 31 | 32 | Controls.gain.EventHandler = function(ctrl) 33 | DKNob.Value = convertToDolby( Controls.gain.Value) 34 | DKNob.EventHandler(Controls.gain) 35 | end 36 | 37 | Controls.ref.EventHandler = function(ctrl) 38 | if Controls.ref.Value == 1 then 39 | DKNob.Value = 7.0 40 | DKNob.EventHandler(Controls.ref) 41 | end 42 | end 43 | 44 | Controls.increase.EventHandler = function() 45 | Step.increase.Value = Controls.increase.Value 46 | end 47 | 48 | Controls.decrease.EventHandler = function() 49 | Step.decrease.Value = Controls.decrease.Value 50 | end 51 | 52 | -- Execute at StartUp 53 | Controls.gain.EventHandler() 54 | DolbyFaderEventHandler = nil 55 | 56 | -------------------------------------------------------------------------------- /Developer/Modules/dolbysweep.lua: -------------------------------------------------------------------------------- 1 | 2 | do 3 | require("qknob") 4 | 5 | function QKnob:SetString(val) 6 | return val .. 's' 7 | end 8 | 9 | local period = QKnob:new('period',1,8,1) 10 | 11 | -- CONSTANTS 12 | 13 | local emul = System.IsEmulating 14 | 15 | local OCTAVE = 11 16 | --local numloops = emul and OCTAVE*6 or OCTAVE*24 17 | local numloops = emul and 130 or 130 * 4 18 | 19 | local start = Controls.start 20 | local enable = Controls.enable 21 | local trigger =Controls.trigger 22 | local mute =Controls.mute 23 | local frequency = Controls.frequency 24 | local level = Controls.level 25 | 26 | -- Local Variables 27 | local timer = Timer.New() 28 | local running = false 29 | local step = 0 30 | 31 | -- Internal funtions & events 32 | 33 | local function Start() 34 | timer:Start(period.Value/numloops) 35 | Sine.mute.Value = 0 36 | running = true 37 | end 38 | 39 | local function Stop() 40 | timer:Stop() 41 | Sine.mute.Value = 1 42 | Sine.level.Position = 0 43 | running = false 44 | end 45 | 46 | local function initplugin() 47 | if start.Value == 0 then 48 | start.Value = 1 49 | level.Value = -40 50 | period.Value = 4 51 | frequency.Value = 20 52 | end 53 | Sine.level.Value = 0 54 | enable.EventHandler() 55 | end 56 | 57 | timer.EventHandler= function(ctimer) 58 | local freq = 10 * 2^( step * OCTAVE/numloops ) 59 | if freq > 22000 then freq = 22000 end 60 | frequency.Value = freq 61 | Sine.frequency.Value = freq 62 | step = step + 1 63 | if freq == 22000 then 64 | step = 0 65 | if enable.Value == 0 then 66 | Stop() 67 | end 68 | end 69 | end 70 | 71 | -- EVENTS 72 | 73 | enable.EventHandler = function(ctrl) 74 | if enable.Value == 0 then 75 | Stop() 76 | else 77 | trigger.EventHandler(enable) 78 | end 79 | end 80 | 81 | trigger.EventHandler = function(ctrl) 82 | if ctrl ~= enable then 83 | Stop() 84 | step = 0 85 | frequency.Value = 10 86 | Sine.frequency.Value = 10 87 | end 88 | Timer.CallAfter(Start,0.1) 89 | Sine.mute.Value = mute.Value 90 | end 91 | 92 | period.EventHandler = function(ctrl) 93 | if running then 94 | Stop() 95 | Timer.CallAfter(Start,0.1) 96 | Sine.mute.Value = mute.Value 97 | end 98 | end 99 | 100 | mute.EventHandler = function(ctrl) 101 | Sine.mute.Value = mute.Value == 1 or not running 102 | end 103 | 104 | 105 | -- PLUGIN INITIALIZATION 106 | 107 | initplugin() 108 | 109 | end 110 | -------------------------------------------------------------------------------- /Developer/plugins/reference.lua: -------------------------------------------------------------------------------- 1 | -- Dolby test Plugin for Q-SYS 2 | -- by James Puig / james.puig@dolby.com 3 | -- Sept '20 4 | 5 | local netbase = '//Mac/Home' --<<-- use in MacOS/Parallels 6 | local base = os.getenv('USERPROFILE') or os.getenv('HOME') 7 | local pathtree = '/Documents/QSC/Q-Sys Designer/Modules' 8 | local path = base .. pathtree .. '/?.lua;' 9 | path = path .. netbase .. pathtree .. '/?.lua;' 10 | path = path .. base .. pathtree .. '/?/init.lua;' 11 | path = path .. netbase .. pathtree .. '/?/init.lua;' 12 | package.path = path .. package.path 13 | 14 | PluginInfo = 15 | { 16 | Name = "test", 17 | Version = "1.0", 18 | BuildVersion = "1.0.0.0", 19 | Id = "afac5c69-fadd-43f6-b9cd-a647d75c684b", 20 | Description = "", 21 | Manufacturer = "", 22 | Author = "James Puig - james.puig@dolby.com", 23 | Type = Reflect and Reflect.Types.AudioIO or 0, 24 | } 25 | 26 | function GetColor(props) 27 | return {204,204,204 } --Is Control 28 | end 29 | 30 | function GetPrettyName(props) 31 | return "test" 32 | end 33 | 34 | function GetProperties() 35 | return { 36 | { 37 | Name = "multi_channel_type", 38 | Type = "integer", 39 | Min = 1, 40 | Max = 3, 41 | Value = 3, 42 | }, 43 | { 44 | Name = "multi_channel_count", 45 | Type = "integer", 46 | Min = 1, 47 | Max = 256, 48 | Value = 4, 49 | } 50 | } 51 | end 52 | 53 | 54 | 55 | 56 | function GetComponents(props) 57 | return 58 | { 59 | { 60 | Name = "mixer", 61 | Type = "mixer", 62 | Properties = 63 | { 64 | n_inputs = 1, 65 | n_outputs = 1, 66 | n_vca_groups = 1, 67 | label_controls = false, 68 | } 69 | }, 70 | { 71 | Name = "Gain", 72 | Type = "gain", 73 | Properties = 74 | { 75 | max_gain = 20, 76 | min_gain = -100, 77 | ["multi_channel_type"] = 3, 78 | ["multi_channel_count"] = 2, 79 | } 80 | }, 81 | { 82 | Name = "Step", 83 | Type = "stepper", 84 | Properties = 85 | { 86 | control_type = 1, --// 0=dB 1=% 2=Integer 87 | num_steps = 100, --// for 2=Integer 88 | max_gain = 10, --// for 0=dB 89 | min_gain = -90, --// for 0=dB 90 | gain_mode = 1, --// for 0=dB 91 | } 92 | }, 93 | { 94 | Name = "Sine", 95 | Type = "sine", 96 | Properties = 97 | { 98 | 99 | } 100 | }, 101 | { 102 | Name = "Router", 103 | Type = "router", 104 | Properties = 105 | { 106 | n_inputs = 1, 107 | n_outputs = 1, 108 | } 109 | }, 110 | { 111 | Name = "control", 112 | Type = "custom_controls", 113 | Properties = 114 | { 115 | 116 | } 117 | }, 118 | { 119 | Name = "meters", 120 | Type = "meter2", 121 | Properties = { 122 | ["meter_type"] = 1, 123 | ["multi_channel_type"] = "Multi-channel", 124 | ["multi_channel_count"] = 2, 125 | }, 126 | }, 127 | { 128 | Name = "FlipFlop", 129 | Type = "flip_flop", 130 | }, 131 | 132 | } 133 | end 134 | 135 | function GetControls(props) 136 | local ctrls = {} 137 | table.insert(ctrls, { 138 | Name = "period", 139 | ControlType = "Text", 140 | }) 141 | table.insert(ctrls, { 142 | Name = "MyLevel", 143 | ControlType = "Knob", 144 | ControlUnit = "Float", 145 | Min = 1, 146 | Max = 5, 147 | }) 148 | return ctrls 149 | 150 | end 151 | 152 | function GetControlLayout(props) 153 | local graphics = {} 154 | local layout = {} 155 | 156 | layout["period"] = { 157 | PrettyName = "Period", 158 | Style = "Knob", 159 | Color={255,0,0}, 160 | Position = { 0,0 }, 161 | Size = { 36,36 }, 162 | } 163 | 164 | layout["MyLevel"] = { 165 | PrettyName = "Level", 166 | Style = "Knob", 167 | Position = { 48,0 }, 168 | Size = { 36,36 }, 169 | } 170 | 171 | return layout, graphics 172 | end 173 | 174 | if Controls or Reflect == nil then 175 | do 176 | end 177 | require(PluginInfo.Name) 178 | end 179 | -------------------------------------------------------------------------------- /Developer/plugins/DolbyFader V1.1.qplug: -------------------------------------------------------------------------------- 1 | -- Dolby Fader for Q-SYS 2 | -- by james.puig@dolby.com 3 | -- Sept '20 4 | 5 | PluginInfo = { 6 | Name = "Dolby~Dolby Fader", 7 | Version = "1.1", 8 | BuildVersion = "1.1.0.1", 9 | Id = "a07f3b34-09af-4c25-af2d-be47d00bb4e9", 10 | Author = "(c) Jaume Puig, Barcelona", 11 | Description = "Dolby Fader Component Plugin", 12 | } 13 | 14 | function GetColor(props) 15 | return { 204, 204, 204 } 16 | end 17 | 18 | function GetPrettyName(props) 19 | return "Dolby Fader " --.. PluginInfo.Version 20 | end 21 | 22 | function GetProperties() 23 | return props 24 | end 25 | 26 | function RectifyProperties(props) 27 | props.plugin_show_debug.IsHidden = true 28 | return props 29 | end 30 | 31 | function GetComponents(props) 32 | local comps = {} 33 | table.insert(comps, 34 | { 35 | Name = "Step", 36 | Type = "stepper", 37 | Properties = 38 | { 39 | control_type = 2, --// Integer 40 | num_steps = 100, 41 | }, 42 | }) 43 | return comps 44 | end 45 | 46 | function GetControls(props) 47 | local ctrls = {} 48 | 49 | table.insert(ctrls, { 50 | Name = "ref", 51 | ControlType = "Button", 52 | ButtonType = "Momentary", 53 | }) 54 | 55 | table.insert(ctrls, { 56 | Name = "level", 57 | ControlType = "Text", 58 | UserPin = true, 59 | PinStyle = "Both", 60 | }) 61 | 62 | table.insert(ctrls, { 63 | Name = "gain", 64 | ControlType = "Knob", 65 | ControlUnit = "dB", 66 | Min = -100, 67 | Max = 20, 68 | PinStyle = "Both", 69 | }) 70 | 71 | table.insert(ctrls, { 72 | Name = "increase", 73 | ControlType = "Button", 74 | ButtonType = "Momentary", 75 | IconType="Icon", 76 | Icon="Plus", 77 | IconColor={0,0,0}, 78 | }) 79 | 80 | table.insert(ctrls, { 81 | Name = "decrease", 82 | ControlType = "Button", 83 | ButtonType = "Momentary", 84 | IconType="Icon", 85 | Icon="Minus", 86 | IconColor={0,0,0}, 87 | }) 88 | return ctrls 89 | end 90 | 91 | function GetControlLayout(props) 92 | local layout = {} 93 | local graphics = {} 94 | local left= -4 95 | local top = -4 96 | 97 | layout['ref'] = { 98 | PrettyName = "Ref Level", 99 | Style = "Button", 100 | ButtonStype = "Momentary", 101 | Color = {242,137,174}, 102 | Size={36,16}, 103 | Position = {left+26,top+48}, 104 | } 105 | layout['level'] = { 106 | PrettyName='Dolby Level', 107 | Style = "Knob", 108 | Color = {0,226,113}, 109 | Position = { left+90, top+28}, 110 | Size={36,36}, 111 | } 112 | layout['increase'] = { 113 | PrettyName = "Increase", 114 | Style = "Button", 115 | ButtonStype = "Momentary", 116 | Color = {255,255,255}, 117 | Position={left+154,top+28}, 118 | Size={36,16}, 119 | } 120 | layout['decrease'] = { 121 | PrettyName = "Decrease", 122 | Style = "Button", 123 | ButtonStype = "Momentary", 124 | Color = {255,255,255}, 125 | Size={36,16}, 126 | Position={left+154,top+48 }, 127 | } 128 | layout["gain"] = { 129 | PrettyName = "Gain", 130 | Style = "Knob", 131 | Position={left+214,top + 28 }, 132 | Size = { 36,36 }, 133 | } 134 | 135 | graphics = { 136 | { 137 | Type="GroupBox", 138 | Text="Knob", 139 | HTextAlign="Left", 140 | StrokeWidth=1, 141 | CornerRadius=8, 142 | Position={left+8,top+8}, 143 | Size={265,100}, 144 | }, 145 | { 146 | Type="Label", 147 | Text="Reference", 148 | Position={left+12,top+72}, 149 | Size={64,16}, 150 | }, 151 | { 152 | Type="Label", 153 | Text="Dolby\nLevel", 154 | Position={left+76, top+72}, 155 | Size={64,32}, 156 | }, 157 | { 158 | Type="Label", 159 | Text="Inc/Dec", 160 | Position={left+140, top + 72}, 161 | Size={64,16}, 162 | }, 163 | { 164 | Type="Label", 165 | Text="RMS Level (dBFS)", 166 | Position={left+202,top+72}, 167 | Size={64,32}, 168 | }, 169 | } 170 | return layout, graphics 171 | end 172 | 173 | if not Controls and Reflect then return end 174 | 175 | require "dolbyfader" 176 | 177 | -------------------------------------------------------------------------------- /Developer/Modules/qknob.lua: -------------------------------------------------------------------------------- 1 | 2 | --/////////////////////// QKNob Class ////////////////////////////// 3 | 4 | require("class/class") 5 | 6 | do 7 | 8 | -- declare local variables & functions 9 | local setposition,getposition,formatstr,formatval,todigits,compare 10 | local privates = setmetatable({}, {__mode = "k"}) 11 | 12 | QKnob = class() 13 | 14 | QKnob:set("Value",{ 15 | value = 0, 16 | get = function(self,value) return value end, 17 | set = function(self, newVal,oldVal) 18 | newVal = formatval(self,newVal) 19 | privates[self].ctrl.Value = newVal 20 | if not privates[self].nested then 21 | privates[self].nested = true 22 | self.Position = setposition(self,newVal) 23 | self.String = newVal 24 | privates[self].nested = false 25 | end 26 | return newVal 27 | end, 28 | } ) 29 | 30 | QKnob:set("String",{ 31 | value = "", 32 | get = function(self,value) return value end, 33 | set = function(self, newVal,oldVal) 34 | newVal = formatstr(self,newVal) 35 | privates[self].ctrl.String = newVal 36 | if not privates[self].nested then 37 | privates[self].nested = true 38 | self.Value = newVal 39 | self.Position = setposition(self,self.Value) 40 | privates[self].nested = false 41 | end 42 | return newVal 43 | end, 44 | }) 45 | 46 | QKnob:set("Position",{ 47 | value = 0, 48 | get = function(self,value) return value end, 49 | set = function(self, newVal,oldVal) 50 | local val = getposition(self,newVal) 51 | newVal = setposition(self,val) 52 | privates[self].ctrl.Position = newVal 53 | if not privates[self].nested then 54 | privates[self].nested = true 55 | self.Value = getposition(self,newVal) 56 | self.String = self.Value 57 | privates[self].nested = false 58 | end 59 | return newVal 60 | end, 61 | } ) 62 | 63 | 64 | function QKnob:SetString(val) 65 | return val 66 | end 67 | 68 | function QKnob:init(ctlName, Min, Max, numDecs ) 69 | local ctrldef = nil 70 | for _,def in ipairs(GetControls(Properties)) do 71 | if def.Name == ctlName then ctrldef = def break end 72 | end 73 | assert(ctrldef,"GetComponents: "..ctlName.." not found") 74 | assert(ctrldef.ControlType=="Text","GetComponents: "..ctlName..".Type is not 'Text'") 75 | privates[self] = { ctrl = Controls[ctlName], nested = false, min=Min, max=Max, 76 | numdecs = numDecs, tm = Timer.New() } 77 | self.EventHandler = nil 78 | privates[self].ctrl.EventHandler = function() 79 | local newVal = formatstr(self,privates[self].ctrl.String) 80 | if self.String ~=newVal then 81 | self.String = newVal 82 | if self.EventHandler then 83 | self.EventHandler(privates[self].ctrl) 84 | end 85 | end 86 | end 87 | self.String = privates[self].ctrl.String 88 | privates[self].tm.EventHandler = function() 89 | compare(self) 90 | end 91 | privates[self].tm:Start(1/1000) 92 | end 93 | 94 | todigits = function (a) 95 | local _ 96 | a = tostring(a) or "" 97 | a,_ = a:gsub("[^%d.-]", "") 98 | return tonumber(a) or 0 99 | end 100 | 101 | setposition = function(self,value) 102 | local p = (value-privates[self].min)/(privates[self].max-privates[self].min) 103 | return p<0 and 1+p or p 104 | end 105 | 106 | getposition = function(self,value) 107 | value = value * ( privates[self].max-privates[self].min ) + privates[self].min 108 | return value 109 | end 110 | 111 | compare = function(self) 112 | if math.abs(privates[self].ctrl.Position - self.Position)>=1e-3 and not privates[self].nested then 113 | self.Position = privates[self].ctrl.Position 114 | if self.EventHandler then 115 | self.EventHandler(privates[self].ctrl) 116 | end 117 | end 118 | end 119 | 120 | formatval = function(self,val) 121 | val = todigits(val) 122 | val = val < privates[self].min and privates[self].min or val 123 | val = val > privates[self].max and privates[self].max or val 124 | if privates[self].numdecs then 125 | val = tonumber(string.format("%."..privates[self].numdecs.."f",val)) 126 | end 127 | return val 128 | end 129 | 130 | formatstr = function(self,val) 131 | val = todigits(val) 132 | val = val < privates[self].min and privates[self].min or val 133 | val = val > privates[self].max and privates[self].max or val 134 | local neg = val<0 and true or false 135 | val = math.abs(val) 136 | local num = 0 137 | num = val<100 and 1 or num 138 | num = val<10 and 2 or num 139 | num = val< 1 and 3 or num 140 | if privates[self].numdecs then 141 | num = num>privates[self].numdecs and privates[self].numdecs or num 142 | end 143 | val = string.format("%."..num.."f",val) 144 | if not privates[self].numdecs then 145 | val = tonumber(val)<1 and val:sub(2) or val 146 | val = tonumber(val)==0 and 0 or val 147 | end 148 | if neg then val = '-'.. val end 149 | val = self:SetString(tostring(val)) 150 | return tostring(val) 151 | end 152 | 153 | end 154 | -------------------------------------------------------------------------------- /Developer/Modules/cpseries.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | -- ############################################################## 4 | -- CPSeries Class 5 | -- ############################################################## 6 | 7 | do 8 | 9 | require("cpseries_class") 10 | require("dolbyfader") 11 | 12 | local DolbyCP = CPSeries.New(Properties.Model.Value) 13 | 14 | DolbyFaderEventHandler = function(ctrl) 15 | if ctrl ~= DolbyCP then 16 | DolbyCP:Action("fader",DKNob.Value) 17 | end 18 | end 19 | 20 | local sock = TcpSocket:New() 21 | sock.WriteTimeout = 2 22 | 23 | -- Local functions 24 | 25 | local function SetStatus(state,msg) 26 | if Controls.status.Value ~= state then 27 | Print(true,'Set Status '..state) 28 | Controls.status.Value = state 29 | end 30 | Controls["status.led"].Value = state 31 | --if Controls.status.Value == 5 then Controls["status.led"].Color = 'blue' 32 | if Controls.status.Value == 0 then Controls["status.led"].Color = 'lightgreen' 33 | --if Controls.status.Value == 2 then Controls["status.led"].Color = 'red' 34 | end 35 | if msg ~= nil then Controls.status.String = string.sub(msg,1,30) 36 | else Controls.status.String = '' end 37 | end 38 | 39 | local function validateAddress(ip) 40 | local chunks = {string.match(ip,"^(%d+)%.(%d+)%.(%d+)%.(%d+)$")} 41 | if #chunks < 4 then 42 | return false 43 | end 44 | for _,v in pairs(chunks) do 45 | if tonumber(v) > 255 then 46 | return false 47 | end 48 | end 49 | return true 50 | end 51 | 52 | local function connect() 53 | Controls.refresh.IsDisabled = false 54 | if validateAddress(Controls.address.String) then 55 | SetStatus(5,'') 56 | local CPPort 57 | local assign = { 58 | [Model.CP650.value] = function() CPPort = 61412 end, 59 | [Model.CP750.value] = function() CPPort = 61408 end, 60 | [Model.CP850.value] = function() CPPort = 61408 end 61 | } assign[Properties.Model.Value]() 62 | 63 | sock:Connect(Controls.address.String,CPPort) 64 | else 65 | if System.IsEmulating then 66 | SetStatus(0,"Emulation") 67 | else 68 | SetStatus(2,"Invalid Address") 69 | end 70 | end 71 | end 72 | 73 | local function disconnect(recon) 74 | DolbyCP:Stop() 75 | sock:Disconnect() 76 | if recon then SetStatus(5,'Connect') 77 | else SetStatus(2,'Offline') end 78 | end 79 | 80 | local function refreshCNX() 81 | disconnect(true) 82 | Controls.refresh.IsDisabled = true 83 | Timer.CallAfter(connect,1) 84 | end 85 | 86 | local function sockError(msg) 87 | if sock.IsConnected then 88 | sock:Disconnect() 89 | Print(true,'SOCK',msg) 90 | Print(true,'SOCK',"Closed") 91 | SetStatus(2,msg) 92 | DolbyCP:Stop() 93 | Timer.CallAfter(refreshCNX,2) 94 | end 95 | end 96 | 97 | --- sock Events -- 98 | 99 | sock.Connected = function() 100 | Print(true,'SOCK',"Connected") 101 | DolbyCP:Start(sock) 102 | end 103 | 104 | sock.Closed = function() 105 | Print(true,'SOCK','Closed') 106 | SetStatus(4,"Offline") 107 | end 108 | 109 | sock.Timeout = function() 110 | Print(true,'SOCK','Timeout') 111 | SetStatus(2,"Timeout") 112 | end 113 | 114 | sock.Error = function(_,err) 115 | Print(true,'SOCK',"Remote Server Error "..err) 116 | SetStatus(2,err) 117 | end 118 | 119 | sock.Reconnect = function() 120 | Print(true,'SOCK',"Reconnecting") 121 | SetStatus(5,"Attempt Reconnect") 122 | end 123 | 124 | -- Events --- 125 | 126 | Controls.address.EventHandler = refreshCNX 127 | Controls.refresh.EventHandler = refreshCNX 128 | 129 | DolbyCP.EventHandler = function(service,result) 130 | local action = { 131 | ["close"] = function() local msg ='unknown Dolby ' ..result sockError(msg) end, 132 | ["ready"] = function() Print(true,'found "Dolby '..result..'"') SetStatus(0) end, 133 | ["formlist"] = function() Controls.select.Choices = result end, 134 | ["formname"] = function() Controls.select.String = result end, 135 | ["mute"] = function() Controls.mute.Value = result end, 136 | ["fader"] = function() DKNob.Value = result 137 | DKNob.EventHandler(DolbyCP) 138 | end, 139 | ["format"] = function() assert(result~=0,"Plugin Event Error: format=0") 140 | Controls.selector[tonumber(result)].Value = 1 141 | Controls.selector[tonumber(result)].EventHandler(DolbyCP) end, 142 | ["reset"] = function() assert(result~=0,"Plugin Event Error: reset=0") 143 | Controls.selector[result].Value = 0 end, 144 | } 145 | -- *** FOR DEBUG **** 146 | --print("Event Triggered:",service,result) 147 | assert( service, "Plugin Event Error: service=nil" ) 148 | assert( result, "Plugin Event Error: result=nil" ) 149 | assert( action[service], "Plugin Event Error: service" ) 150 | action[service]() 151 | end 152 | 153 | Controls.mute.EventHandler = function(ctrl) 154 | DolbyCP:Action("mute",Controls.mute.Value) 155 | end 156 | 157 | Controls.select.EventHandler = function(ctrl) 158 | DolbyCP:Action("formname",Controls.select.String) 159 | end 160 | 161 | for numBtn,ctl in ipairs(Controls.selector) do 162 | ctl.EventHandler = function(ctrl) 163 | if ctl.Value == 1 then 164 | for _,ctrl in ipairs(Controls.selector) do 165 | if ctrl ~= ctl then ctrl.Value = 0 end 166 | end 167 | if ctrl ~= DolbyCP then DolbyCP:Action("format",numBtn) end 168 | else 169 | for _,ctl in ipairs(Controls.selector) do 170 | if ctl.Value == 1 then return end 171 | end 172 | DolbyCP:Action("reset") 173 | end 174 | end end 175 | 176 | 177 | 178 | -- init plugin-- 179 | 180 | -- hidden components 181 | 182 | if Controls.start.Value == false then 183 | Controls.start.Value = true 184 | dolby.Value = 7.0 185 | dolby.EventHandler(DolbyCP) 186 | Controls.mute.Value = 0 187 | Controls.selector[1].Value = 1 188 | Controls.selector[1].EventHandler(DolbyCP) 189 | end 190 | 191 | refreshCNX() 192 | 193 | end -------------------------------------------------------------------------------- /Developer/plugins/Dolby Sweep V1.1.qplug: -------------------------------------------------------------------------------- 1 | -- Dolby Sweep Generator Plugin for Q-SYS 2 | -- by James Puig / james.puig@dolby.com 3 | -- Jul '20 4 | 5 | PluginInfo = 6 | { 7 | Name = "Dolby~Dolby Sweep", 8 | Version = "1.0", 9 | BuildVersion = "1.0.4.1", 10 | Id = "0e5ea38f-6c26-469b-aa3b-00503f1f263a", 11 | Description = "Dolby Sweep Generator", 12 | Manufacturer = "Dolby", 13 | Author = "James Puig - james.puig@dolby.com", 14 | Type = Reflect and Reflect.Types.AudioIO or 0, 15 | } 16 | 17 | 18 | 19 | function GetColor(props) 20 | return {164,212,176 } --Is Audio 21 | end 22 | 23 | function GetPrettyName(props) 24 | return "Dolby Sweep Generator" 25 | end 26 | 27 | function GetProperties() 28 | props = {} 29 | return { 30 | 31 | { 32 | Name = "Type", 33 | Type = "enum", 34 | Choices = { "Mono", "Stereo","Multi-channel" }, 35 | Value = "Mono", 36 | }, 37 | { 38 | Name = "Count", 39 | Type = "integer", 40 | Value = 8, 41 | Min = 2, 42 | Max=256, 43 | }, 44 | 45 | } 46 | end 47 | 48 | function RectifyProperties(props) 49 | props.Count.IsHidden = props.Type.Value ~= "Multi-channel" 50 | props.plugin_show_debug.IsHidden = true 51 | return props 52 | end 53 | 54 | function GetComponents(props) 55 | return { { Name = "Sine", Type = "sine" } } 56 | end 57 | 58 | local function getPinsNames(props) 59 | local names = {} 60 | if props.Type.Value == "Multi-channel" then 61 | for num = 1,props.Count.Value do 62 | table.insert(names,string.format("Output Channel %i", num)) end 63 | elseif props.Type.Value == "Stereo" then 64 | table.insert(names,"Output Left") 65 | table.insert(names,"Output Right") 66 | else table.insert(names,"Output") end 67 | return names 68 | end 69 | 70 | function GetPins(props) 71 | local pins = {} 72 | local names = getPinsNames(props) 73 | for _,name in pairs(names) do 74 | table.insert(pins, { Name = name, Direction = "output" } ) 75 | end 76 | return pins 77 | end 78 | 79 | function GetWiring(props) 80 | local wiring = {} 81 | local names = getPinsNames(props) 82 | for _,name in pairs(names) do 83 | table.insert( wiring, { "Sine Output", name } ) 84 | end 85 | return wiring 86 | end 87 | 88 | function GetControls(props) 89 | return { 90 | { 91 | Name = "start", 92 | ControlType = "Button", 93 | ButtonType = "Toggle", 94 | }, 95 | { 96 | Name = "enable", 97 | ControlType = "Button", 98 | ButtonType = "Toggle", 99 | UserPin = true, 100 | PinStyle = "Both", 101 | }, 102 | { 103 | Name = "trigger", 104 | ControlType = "Button", 105 | ButtonType = "Trigger", 106 | UserPin = true, 107 | PinStyle = "Both", 108 | }, 109 | { 110 | Name = "mute", 111 | ControlType = "Button", 112 | ButtonType = "Toggle", 113 | UserPin = true, 114 | PinStyle = "Both", 115 | }, 116 | { 117 | Name = "period", 118 | ControlType = "Text", 119 | }, 120 | { 121 | Name = "frequency", 122 | ControlType = "Knob", 123 | ControlUnit ="Hz", 124 | Min = 10, 125 | Max = 22000, 126 | UserPin = true, 127 | PinStyle = "Output", 128 | }, 129 | { 130 | Name = "level", 131 | ControlType = "Knob", 132 | ControlUnit ="dB", 133 | Min = -100, 134 | Max = 20, 135 | UserPin = true, 136 | PinStyle = "Both", 137 | }, 138 | } 139 | end 140 | 141 | function GetControlLayout(props) 142 | local left1=5 143 | local left2=149 144 | local top =5 145 | local graphics = { 146 | { 147 | Type="GroupBox", 148 | Text="Run", 149 | HTextAlign="Left", 150 | StrokeWidth=1, 151 | CornerRadius=8, 152 | Position={left1,top}, 153 | Size={136,100}, 154 | }, 155 | { 156 | Type="GroupBox", 157 | Text="Sweep", 158 | HTextAlign="Left", 159 | StrokeWidth=1, 160 | CornerRadius=8, 161 | Position={left2,top}, 162 | Size={208+64,100}, 163 | }, 164 | { 165 | Type="Label", 166 | Text="Free-Run", 167 | HTextAlign="Center", 168 | VTextAlign="Center", 169 | StrokeWidth=0, 170 | CornerRadius=0, 171 | Position={ left1+4, top+40+24} , 172 | Size={64,16}, 173 | }, 174 | { 175 | Type="Label", 176 | Text="One-Shot/Sync", 177 | HTextAlign="Center", 178 | VTextAlign="Center", 179 | StrokeWidth=0, 180 | CornerRadius=0, 181 | Position= { left1+4+64, top+40+24 }, 182 | Size={64,32}, 183 | }, 184 | { 185 | Type="Label", 186 | Text="Mute", 187 | HTextAlign="Center", 188 | VTextAlign="Center", 189 | StrokeWidth=0, 190 | CornerRadius=0, 191 | Position= { left2+4, top+40+24 }, 192 | Size={64,16}, 193 | }, 194 | { 195 | Type="Label", 196 | Text="Period", 197 | HTextAlign="Center", 198 | VTextAlign="Center", 199 | StrokeWidth=0, 200 | CornerRadius=0, 201 | Position= { left2+4+64, top+40+24 }, 202 | Size={64,16}, 203 | }, 204 | { 205 | Type="Label", 206 | Text="Frequency", 207 | HTextAlign="Center", 208 | VTextAlign="Center", 209 | StrokeWidth=0, 210 | CornerRadius=0, 211 | Position= { left2+4+128, top+40+24 }, 212 | Size={64,16}, 213 | }, 214 | { 215 | Type="Label", 216 | Text="RMS Level (dBFS)", 217 | HTextAlign="Center", 218 | VTextAlign="Center", 219 | StrokeWidth=0, 220 | CornerRadius=0, 221 | Position= { left2+4+128+64, top+40+24 }, 222 | Size={64,32}, 223 | }, 224 | } 225 | left1 = left1 + 18 226 | left2 = left2 + 18 227 | top = top + 20 228 | local layout = {} 229 | layout["enable"] = { 230 | PrettyName = "Enable", 231 | Style = "Button", 232 | ButtonStyle = "Toggle", 233 | Position = { left1,top+20 }, 234 | Color = { 242, 137, 174 }, 235 | Size = { 36,16 }, 236 | } 237 | layout["trigger"] = { 238 | PrettyName = "Trigger", 239 | Style = "Button", 240 | ButtonStyle = "Trigger", 241 | Position = { left1+64,top+20 }, 242 | Color = { 255, 255, 255 }, 243 | Size = { 36,16 }, 244 | } 245 | layout["mute"] = { 246 | PrettyName = "Mute", 247 | Style = "Button", 248 | ButtonStyle = "Toggle", 249 | Position = { left2,top+20 }, 250 | Color = { 223, 0, 36 }, 251 | Size = { 36,16 }, 252 | } 253 | layout["period"] = { 254 | PrettyName = "Duty Cycle", 255 | Style = "Knob", 256 | Color = { 254, 248, 174 }, 257 | Position = { left2+64,top }, 258 | Size = { 36,36 }, 259 | } 260 | layout["frequency"] = { 261 | PrettyName = "Frequency", 262 | Style = "Knob", 263 | IsReadOnly = true, 264 | Position = { left2+128,top }, 265 | Size = { 36,36 }, 266 | } 267 | layout["level"] = { 268 | PrettyName = "RMS Level (dBFS)", 269 | Style = "Knob", 270 | Position = { left2+128+64,top }, 271 | Size = { 36,36 }, 272 | } 273 | return layout, graphics 274 | end 275 | 276 | if not Controls and Reflect then return end 277 | 278 | require "dolbysweep" 279 | -------------------------------------------------------------------------------- /Developer/plugins/Dolby CPSeries Control V2.2.qplug: -------------------------------------------------------------------------------- 1 | -- CP Series Control Plugin for Q-SYS 2 | -- by James Puig / james.puig@dolby.com 3 | -- Febr '20 4 | 5 | PluginInfo = 6 | { 7 | Name = "Dolby~CP Series Control", 8 | Version = "2.2", 9 | BuildVersion = "2.2.0.1", 10 | Id = "349d7196-f9b3-4293-8571-6d81ea6971da", 11 | Description = "QSys Control Plugin for Dolby CP Series Cinema Processors", 12 | Manufacturer = "Dolby", 13 | Author = "James Puig - james.puig@dolby.com", 14 | } 15 | 16 | -- setmeta for tables -- 17 | 18 | function setmeta(table) 19 | local function searchelem(table,index) local m = getmetatable(table) 20 | local list = m.__table or table for count,elem in pairs(list) do 21 | for key,value in pairs(elem) do if elem == table then if index=="index" then return count end 22 | if index=="key" then return key end if index=="value" then return value end end 23 | if key == index then if index~="state" then return elem end return nil end end end 24 | error("variable '"..index.."' is not declared", 2) end 25 | local m = getmetatable(table) or {} setmetatable(table,m) 26 | m.__index = searchelem m = {} m.__index = searchelem m.__table = table 27 | for _,elem in pairs(table) do setmetatable(elem,m) end end 28 | 29 | -- Constants -- 30 | 31 | Model = { {CP650='CP 650'},{CP750='CP 750'},{CP850='CP 850'} } 32 | setmeta(Model) 33 | 34 | local ButtonLabel = { {'01','04','05','10','11','U1','U2','NS'}, 35 | {'Dig1','Dig2','Dig3','Dig4','Ana','NS','Mic'}, 36 | { '1','2','3','4','5','6','7','8' } } 37 | 38 | -- Control Functions 39 | 40 | function GetColor(props) 41 | return {0,127,255 } 42 | end 43 | 44 | function GetPrettyName(props) 45 | return "Dolby "..props.Model.Value.." Control" 46 | end 47 | 48 | function GetProperties() 49 | local props = {} 50 | local list = {} 51 | for _,elem in pairs(Model) do 52 | table.insert(list,elem.value) 53 | end 54 | table.insert(props, { Name="Model" ,Type = "enum",Choices = list, Value = Model.CP850.value}) 55 | table.insert(props, { Name="TCP Log" ,Type = "enum",Choices = { 'Command', 'All' }, Value = 'Command' }) 56 | return props 57 | end 58 | 59 | function RectifyProperties(props) 60 | props["TCP Log"].IsHidden = not props.plugin_show_debug.Value 61 | return props 62 | end 63 | 64 | function GetComponents(props) 65 | return { 66 | { 67 | Name = "Step", 68 | Type = "stepper", 69 | Properties = 70 | { 71 | control_type = 2, 72 | num_steps = 100, 73 | } 74 | }, 75 | } 76 | end 77 | 78 | function GetControls(props) 79 | local numButtons = props.Model.Value == Model.CP750.value and 7 or 8 80 | return { 81 | { Name = "start" }, 82 | { Name = "ref",ControlType = "Button",ButtonType = "Momentary"}, 83 | { Name = "level",ControlType = "Text",UserPin = true,PinStyle = "Both"}, 84 | { Name = "gain",ControlType = "Knob",ControlUnit = "dB",Min = -100,Max = 20,UserPin=true, PinStyle = "Both"}, 85 | { Name = "increase",ControlType = "Button",ButtonType = "Momentary",IconType="Icon",Icon="Plus", IconColor={0,0,0} }, 86 | { Name = "decrease",ControlType = "Button",ButtonType = "Momentary",IconType="Icon",Icon="Minus",IconColor={0,0,0} }, 87 | { Name="address", ControlType="Text",Count=1, UserPin=false }, 88 | { Name = "status", ControlType="Indicator", IndicatorType="Status", UserPin=true, PinStyle='Output' }, 89 | { Name = "status.led", ControlType="Indicator", IndicatorType="Status" }, 90 | { Name = "refresh", ControlType="Button", ButtonType = "Trigger", Icon='Refresh'}, 91 | { Name = "select", ControlType = "Text",UserPin = true,PinStyle='Both' }, 92 | { Name = "selector", ControlType="Button", ButtonType="Toggle", Count=numButtons,UserPin = true,PinStyle='Both' }, 93 | { Name = "mute", ControlType="Button", ButtonType="Toggle", Icon ='Volume Strike', UserPin=true, PinStyle='Both', }, 94 | } 95 | end 96 | 97 | function GetControlLayout(props) 98 | local layout = {} 99 | local graphics = {} 100 | local col = 70 101 | local row = 6 102 | local gutter = 50 103 | local numButtons = props.Model.Value == Model.CP750.value and 7 or 8 104 | table.insert(graphics, { Type="GroupBox", CornerRadius=5, Fill={255,245,232,255}, StrokeWidth = 1, StrokeColor = {241,199,245}, Position={-5,-5}, Size={358,368}, Padding=0, Margin=0 }) 105 | table.insert(graphics, { Type="Label", Text=props.Model.Value, Fill={255,245,232,255}, Size={ 160,28 }, Position={ 46+gutter,row}, IsBold = true, FontSize=24 }) 106 | row = row + 40 107 | table.insert(graphics, { Type = "Text", HTextAlign="Right", Text = "IP:", Position={ gutter, row }, Size={ 44,16 } }) 108 | layout['address'] = { PrettyName='Address', Position={gutter+50,row}, Size={ 140,16 } } 109 | layout['refresh'] = { PrettyName='Refresh Connection', FontStyle='Black', Position={gutter+190,row}, Size={ 36,16 }, Margin=0, } 110 | row = row+32 111 | table.insert(graphics, { Type="GroupBox", HTextAlign="Left", Text="Status", StrokeWidth=1, CornerRadius=8, Position={ 10, row-5 }, Size={ 330,64} }) 112 | layout['status.led'] = { PrettyName='CP Status', Style="Led", Margin=3, Position={gutter-14,row+22}, Size={ 16,16 } } 113 | layout['status'] = { PrettyName='CP Status', Position={gutter+10,row+14}, Size={ 262,32 } } 114 | row = row+48+24 115 | local left= 0 116 | local top = row 117 | layout['ref'] = { PrettyName = "Ref Level", Style = "Button", ButtonStype = "Momentary", Color = {242,137,174}, Size={36,16}, Position = {left+26,top+48}, } 118 | layout['level'] = { PrettyName='Dolby Level', Style = "Knob", Color = {0,226,113}, Position = { left+90, top+28}, Size={36,36}, } 119 | layout['increase'] = { PrettyName = "Increase", Style = "Button", ButtonStype = "Momentary", Color = {255,255,255}, Position={left+154,top+28}, Size={36,16}, } 120 | layout['decrease'] = { PrettyName = "decrease", Style = "Button", ButtonStype = "Momentary", Color = {255,255,255}, Size={36,16}, Position={left+154,top+48 }, } 121 | layout["gain"] = { PrettyName = "Gain", Style = "Knob", Position={left+214,top + 28 }, Size = { 36,36 }, } 122 | layout['mute'] = { PrettyName='Mute', CornerRadius=20, Color={255,0,0}, UnlinkOffColor=true, OffColor={100,100,100}, Padding=10, Radius=5, Position={left+214+64,top + 22 }, Size={46,46} } 123 | table.insert(graphics, { Type="GroupBox", Text="Level", HTextAlign="Left", StrokeWidth=1, CornerRadius=8, Position={left+10,top+8}, Size={330,100}, }) 124 | table.insert(graphics, { Type="Label", Text="Reference", Position={left+12,top+72}, Size={64,16}, }) 125 | table.insert(graphics, { Type="Label", Text="Dolby\nLevel", Position={left+76, top+72}, Size={64,32}, }) 126 | table.insert(graphics, { Type="Label", Text="Inc/Dec", Position={left+140, top + 72}, Size={64,16}, }) 127 | table.insert(graphics, { Type="Label", Text="RMS Level (dBFS)", Position={left+202,top+72}, Size={64,32}, }) 128 | table.insert(graphics, { Type="Label", Text="Mute", Position={left+202+64,top+72}, Size={64,32}, }) 129 | row = row + 130 130 | table.insert(graphics, { Type="Text", Text="Format:", HTextAlign = "Right", Position={ gutter-46, row+1 }, Size={ 90,16 } }) 131 | layout['select']= { PrettyName='Select', Style='ComboBox', TextFontSize=12, Position={gutter+50,row}, Size={170,18} } 132 | row = row+32 col = 36 133 | local btnLayout,macro,proc 134 | local assign = { 135 | [Model.CP650.value] = function() macro = 'Button' proc=Model.CP650.index btnLayout = ButtonLabel[1] end, 136 | [Model.CP750.value] = function() macro = 'Button' proc=Model.CP750.index btnLayout = ButtonLabel[2] end, 137 | [Model.CP850.value] = function() macro = 'Macro' proc=Model.CP850.index btnLayout = ButtonLabel[3] end 138 | } assign[props.Model.Value]() 139 | local v = props.Model.Value == Model.CP750.value and gutter or gutter-20 140 | for i = 1,numButtons do 141 | layout['selector '..i]={ PrettyName= macro ..'~'..ButtonLabel[proc][i], Legend = btnLayout[i], Position={v,row}, 142 | Color={0,255,0}, FontStyle='Bold', fontSize=14, CornerRadius=20, ButtonStyle="On", Size={36,36} } 143 | v = v+col 144 | end 145 | return layout, graphics 146 | end 147 | 148 | if not Controls and Reflect then return end 149 | 150 | require "CPSeries" 151 | -------------------------------------------------------------------------------- /MultiFlip-Flop.qplug: -------------------------------------------------------------------------------- 1 | -- Multi Flip-Flop for Q-SYS 2 | -- by James Puig / james.puig@dolby.com 3 | -- Jul '20 4 | 5 | PluginInfo = { 6 | Name = "Dolby~Multi Flip-Flop", 7 | Version = "1.0", 8 | BuildVersion = "1.0.1.31", 9 | Id = "8d8af033-fa93-4962-805a-96d57b435859", 10 | Author = "Jaume Puig", 11 | Description = "Advanced Multi Flip-Flop Component Plugin", 12 | } 13 | 14 | function GetColor(props) 15 | return { 204, 204, 204 } 16 | end 17 | 18 | function GetPrettyName(props) 19 | return "Multi Flip-Flop " -- .. PluginInfo.Version 20 | end 21 | 22 | function GetProperties() 23 | local props = {} 24 | 25 | table.insert(props, 26 | { 27 | Name = "Input Count", 28 | Type = "integer", 29 | Value = 1, 30 | Min = 1, 31 | Max=256, 32 | } 33 | ) 34 | return props 35 | end 36 | 37 | function RectifyProperties(props) 38 | props.plugin_show_debug.IsHidden = true 39 | return props 40 | end 41 | 42 | function GetControls(props) 43 | local ctrls = {} 44 | 45 | table.insert(ctrls, { 46 | Name = "start", 47 | ControlType = "Button", 48 | ButtonType = "Toggle", 49 | }) 50 | 51 | table.insert(ctrls, { 52 | Name = "exclusive", 53 | ControlType = "Button", 54 | ButtonType = "Toggle", 55 | }) 56 | 57 | for t = 1,props["Input Count"].Value do 58 | 59 | table.insert(ctrls, { 60 | Name = "set_"..t, 61 | ControlType = "Button", 62 | ButtonType = "Trigger", 63 | UserPin = true, 64 | PinStyle = "Both", 65 | }) 66 | 67 | table.insert(ctrls, { 68 | Name = "reset_"..t, 69 | ControlType = "Button", 70 | ButtonType = "Trigger", 71 | UserPin = true, 72 | PinStyle = "Both", 73 | }) 74 | 75 | table.insert(ctrls, { 76 | Name = "toggle_"..t, 77 | ControlType = "Button", 78 | ButtonType = "Trigger", 79 | UserPin = true, 80 | PinStyle = "Both", 81 | }) 82 | 83 | table.insert(ctrls, { 84 | Name = "state_"..t, 85 | ControlType = "Button", 86 | ButtonType = "Toggle", 87 | UserPin = true, 88 | PinStyle = "Both", 89 | }) 90 | 91 | table.insert (ctrls, { 92 | Name = "led_"..t, 93 | ControlType="Indicator", 94 | IndicatorType="Led", 95 | Count=2, 96 | UserPin = true, 97 | PinStyle = "Both", 98 | }) 99 | 100 | table.insert (ctrls, { 101 | Name = "out_"..t, 102 | ControlType="Indicator", 103 | IndicatorType="Led", 104 | UserPin = true, 105 | PinStyle = "Output", 106 | }) 107 | 108 | table.insert (ctrls, { 109 | Name = "not_"..t, 110 | ControlType="Indicator", 111 | IndicatorType="Led", 112 | UserPin = true, 113 | PinStyle = "Output", 114 | }) 115 | end 116 | return ctrls 117 | end 118 | 119 | function GetControlLayout(props) 120 | local layout = {} 121 | local graphics = {} 122 | 123 | local top = 20 124 | 125 | table.insert(graphics, { 126 | Type="Label", 127 | Text="Exclusive", 128 | StrokeWidth=0, 129 | VAlign = Center, 130 | Position={ 144,0} , 131 | Size={64,16}, 132 | }) 133 | 134 | table.insert(graphics, { 135 | Type="Label", 136 | Text="Set", 137 | StrokeWidth=0, 138 | VAlign = Center, 139 | Position={ 36*1,top} , 140 | Size={36,32}, 141 | }) 142 | 143 | table.insert(graphics, { 144 | Type="Label", 145 | Text="Reset", 146 | StrokeWidth=0, 147 | VAlign = Center, 148 | Position={ 36*2,top} , 149 | Size={36,32}, 150 | }) 151 | 152 | table.insert(graphics, { 153 | Type="Label", 154 | Text="Toggle", 155 | StrokeWidth=0, 156 | VAlign = Center, 157 | Position={36*3,top} , 158 | Size={36,32}, 159 | }) 160 | 161 | table.insert(graphics, { 162 | Type="Label", 163 | Text="State", 164 | StrokeWidth=0, 165 | VAlign = Center, 166 | Position={36*4,top} , 167 | Size={36,32}, 168 | }) 169 | 170 | table.insert(graphics, { 171 | Type="Label", 172 | Text="Out", 173 | VAlign = Center, 174 | StrokeWidth=0, 175 | Position={ 36*5+4,top} , 176 | Size={36,32}, 177 | }) 178 | 179 | table.insert(graphics, { 180 | Type="Label", 181 | Text="Not Out", 182 | StrokeWidth=0, 183 | VAlign = Center, 184 | Position={ 36*6+8,top} , 185 | Size={36,32}, 186 | }) 187 | 188 | for t = 1,props["Input Count"].Value do 189 | 190 | table.insert(graphics, { 191 | Type="Label", 192 | Text=tostring(t), 193 | StrokeWidth=0, 194 | Position={0,top + (t+1)*16} , 195 | Size={32,16}, 196 | }) 197 | 198 | layout['exclusive'] = { PrettyName = "Exclusive", Position={ 210,0 }, } 199 | layout['set_'..t] = { PrettyName = tostring(t).."~Set", Position={ 36*1,top+(t+1)*16} } 200 | layout['reset_'..t] = { PrettyName = tostring(t).."~Reset",Position={ 36*2,top+(t+1)*16} } 201 | layout['toggle_'..t] = { PrettyName = tostring(t).."~Toggle",Position={ 36*3,top+(t+1)*16} } 202 | layout['state_'..t] = { PrettyName = tostring(t).."~State",Position={ 36*4,top+(t+1)*16} } 203 | layout['out_'..t] = { PrettyName = tostring(t).."~Out",Position={ 36*5+16,top+(t+1)*16} } 204 | layout['not_'..t] = { PrettyName = tostring(t).."~Not out",Position={ 36*6+16,top+(t+1)*16} } 205 | end 206 | return layout, graphics 207 | end 208 | 209 | --Start event based logic 210 | if Controls then 211 | 212 | local function reset(t) 213 | if Controls.exclusive.Value == 0 then return end 214 | for v = 1,Properties["Input Count"].Value do 215 | if v ~= t then 216 | Controls["reset_"..v]:Trigger() 217 | Controls["state_"..v].Value = 0 218 | Controls['out_'..v].Value = 0 219 | Controls['not_'..v].Value = 1 220 | end 221 | end 222 | end 223 | 224 | Controls.exclusive.EventHandler = function(ctrl) 225 | local first = false 226 | for t = 1,Properties["Input Count"].Value do 227 | if first then 228 | Controls["state_"..t].Value = 0 229 | Controls["reset_"..t]:Trigger() 230 | Controls['out_'..t].Value = 0 231 | Controls['not_'..t].Value = 1 232 | elseif Controls["state_"..t].Value == 1 then 233 | first = true 234 | end 235 | end 236 | end 237 | 238 | for t = 1,Properties["Input Count"].Value do 239 | 240 | Controls["state_"..t].EventHandler = function(ctrl) 241 | reset(t) 242 | if Controls["state_"..t].Value == 1 then 243 | Controls["set_"..t]:Trigger() 244 | Controls['out_'..t].Value = 1 245 | Controls['not_'..t].Value = 0 246 | else 247 | Controls["reset_"..t]:Trigger() 248 | Controls['out_'..t].Value = 0 249 | Controls['not_'..t].Value = 1 250 | end 251 | end 252 | 253 | Controls["set_"..t].EventHandler = function(ctrl) 254 | reset(t) 255 | Controls["state_"..t].Value = 1 256 | Controls['out_'..t].Value = 1 257 | Controls['not_'..t].Value = 0 258 | end 259 | 260 | Controls["reset_"..t].EventHandler = function(ctrl) 261 | Controls["state_"..t].Value = 0 262 | Controls['out_'..t].Value = 0 263 | Controls['not_'..t].Value = 1 264 | end 265 | 266 | Controls["toggle_"..t].EventHandler = function(ctrl) 267 | Controls["state_"..t].Value = Controls["state_"..t].Value == 0 268 | Controls["state_"..t].EventHandler() 269 | end 270 | 271 | end 272 | 273 | for t = 1,Properties["Input Count"].Value do 274 | if Controls["state_"..t].Value == 1 then 275 | Controls["set_"..t]:Trigger() 276 | Controls['out_'..t].Value = 1 277 | Controls['not_'..t].Value = 0 278 | else 279 | Controls["reset_"..t]:Trigger() 280 | Controls['out_'..t].Value = 0 281 | Controls['not_'..t].Value = 1 282 | end 283 | 284 | end 285 | end 286 | -------------------------------------------------------------------------------- /Developer/plugins/MultiFlip-Flop V1.1.qplug: -------------------------------------------------------------------------------- 1 | -- Multi Flip-Flop for Q-SYS 2 | -- by James Puig / james.puig@dolby.com 3 | -- Jul '20 4 | 5 | PluginInfo = { 6 | Name = "Dolby~Multi Flip-Flop", 7 | Version = "1.0", 8 | BuildVersion = "1.0.1.31", 9 | Id = "8d8af033-fa93-4962-805a-96d57b435859", 10 | Author = "Jaume Puig", 11 | Description = "Advanced Multi Flip-Flop Component Plugin", 12 | } 13 | 14 | function GetColor(props) 15 | return { 204, 204, 204 } 16 | end 17 | 18 | function GetPrettyName(props) 19 | return "Multi Flip-Flop " -- .. PluginInfo.Version 20 | end 21 | 22 | function GetProperties() 23 | local props = {} 24 | 25 | table.insert(props, 26 | { 27 | Name = "Input Count", 28 | Type = "integer", 29 | Value = 1, 30 | Min = 1, 31 | Max=256, 32 | } 33 | ) 34 | return props 35 | end 36 | 37 | function RectifyProperties(props) 38 | props.plugin_show_debug.IsHidden = true 39 | return props 40 | end 41 | 42 | function GetControls(props) 43 | local ctrls = {} 44 | 45 | table.insert(ctrls, { 46 | Name = "start", 47 | ControlType = "Button", 48 | ButtonType = "Toggle", 49 | }) 50 | 51 | table.insert(ctrls, { 52 | Name = "exclusive", 53 | ControlType = "Button", 54 | ButtonType = "Toggle", 55 | }) 56 | 57 | for t = 1,props["Input Count"].Value do 58 | 59 | table.insert(ctrls, { 60 | Name = "set_"..t, 61 | ControlType = "Button", 62 | ButtonType = "Trigger", 63 | UserPin = true, 64 | PinStyle = "Both", 65 | }) 66 | 67 | table.insert(ctrls, { 68 | Name = "reset_"..t, 69 | ControlType = "Button", 70 | ButtonType = "Trigger", 71 | UserPin = true, 72 | PinStyle = "Both", 73 | }) 74 | 75 | table.insert(ctrls, { 76 | Name = "toggle_"..t, 77 | ControlType = "Button", 78 | ButtonType = "Trigger", 79 | UserPin = true, 80 | PinStyle = "Both", 81 | }) 82 | 83 | table.insert(ctrls, { 84 | Name = "state_"..t, 85 | ControlType = "Button", 86 | ButtonType = "Toggle", 87 | UserPin = true, 88 | PinStyle = "Both", 89 | }) 90 | 91 | table.insert (ctrls, { 92 | Name = "led_"..t, 93 | ControlType="Indicator", 94 | IndicatorType="Led", 95 | Count=2, 96 | UserPin = true, 97 | PinStyle = "Both", 98 | }) 99 | 100 | table.insert (ctrls, { 101 | Name = "out_"..t, 102 | ControlType="Indicator", 103 | IndicatorType="Led", 104 | UserPin = true, 105 | PinStyle = "Output", 106 | }) 107 | 108 | table.insert (ctrls, { 109 | Name = "not_"..t, 110 | ControlType="Indicator", 111 | IndicatorType="Led", 112 | UserPin = true, 113 | PinStyle = "Output", 114 | }) 115 | end 116 | return ctrls 117 | end 118 | 119 | function GetControlLayout(props) 120 | local layout = {} 121 | local graphics = {} 122 | 123 | local top = 20 124 | 125 | table.insert(graphics, { 126 | Type="Label", 127 | Text="Exclusive", 128 | StrokeWidth=0, 129 | VAlign = Center, 130 | Position={ 144,0} , 131 | Size={64,16}, 132 | }) 133 | 134 | table.insert(graphics, { 135 | Type="Label", 136 | Text="Set", 137 | StrokeWidth=0, 138 | VAlign = Center, 139 | Position={ 36*1,top} , 140 | Size={36,32}, 141 | }) 142 | 143 | table.insert(graphics, { 144 | Type="Label", 145 | Text="Reset", 146 | StrokeWidth=0, 147 | VAlign = Center, 148 | Position={ 36*2,top} , 149 | Size={36,32}, 150 | }) 151 | 152 | table.insert(graphics, { 153 | Type="Label", 154 | Text="Toggle", 155 | StrokeWidth=0, 156 | VAlign = Center, 157 | Position={36*3,top} , 158 | Size={36,32}, 159 | }) 160 | 161 | table.insert(graphics, { 162 | Type="Label", 163 | Text="State", 164 | StrokeWidth=0, 165 | VAlign = Center, 166 | Position={36*4,top} , 167 | Size={36,32}, 168 | }) 169 | 170 | table.insert(graphics, { 171 | Type="Label", 172 | Text="Out", 173 | VAlign = Center, 174 | StrokeWidth=0, 175 | Position={ 36*5+4,top} , 176 | Size={36,32}, 177 | }) 178 | 179 | table.insert(graphics, { 180 | Type="Label", 181 | Text="Not Out", 182 | StrokeWidth=0, 183 | VAlign = Center, 184 | Position={ 36*6+8,top} , 185 | Size={36,32}, 186 | }) 187 | 188 | for t = 1,props["Input Count"].Value do 189 | 190 | table.insert(graphics, { 191 | Type="Label", 192 | Text=tostring(t), 193 | StrokeWidth=0, 194 | Position={0,top + (t+1)*16} , 195 | Size={32,16}, 196 | }) 197 | 198 | layout['exclusive'] = { PrettyName = "Exclusive", Position={ 210,0 }, } 199 | layout['set_'..t] = { PrettyName = tostring(t).."~Set", Position={ 36*1,top+(t+1)*16} } 200 | layout['reset_'..t] = { PrettyName = tostring(t).."~Reset",Position={ 36*2,top+(t+1)*16} } 201 | layout['toggle_'..t] = { PrettyName = tostring(t).."~Toggle",Position={ 36*3,top+(t+1)*16} } 202 | layout['state_'..t] = { PrettyName = tostring(t).."~State",Position={ 36*4,top+(t+1)*16} } 203 | layout['out_'..t] = { PrettyName = tostring(t).."~Out",Position={ 36*5+16,top+(t+1)*16} } 204 | layout['not_'..t] = { PrettyName = tostring(t).."~Not out",Position={ 36*6+16,top+(t+1)*16} } 205 | end 206 | return layout, graphics 207 | end 208 | 209 | --Start event based logic 210 | if Controls then 211 | 212 | local function reset(t) 213 | if Controls.exclusive.Value == 0 then return end 214 | for v = 1,Properties["Input Count"].Value do 215 | if v ~= t then 216 | Controls["reset_"..v]:Trigger() 217 | Controls["state_"..v].Value = 0 218 | Controls['out_'..v].Value = 0 219 | Controls['not_'..v].Value = 1 220 | end 221 | end 222 | end 223 | 224 | Controls.exclusive.EventHandler = function(ctrl) 225 | local first = false 226 | for t = 1,Properties["Input Count"].Value do 227 | if first then 228 | Controls["state_"..t].Value = 0 229 | Controls["reset_"..t]:Trigger() 230 | Controls['out_'..t].Value = 0 231 | Controls['not_'..t].Value = 1 232 | elseif Controls["state_"..t].Value == 1 then 233 | first = true 234 | end 235 | end 236 | end 237 | 238 | for t = 1,Properties["Input Count"].Value do 239 | 240 | Controls["state_"..t].EventHandler = function(ctrl) 241 | reset(t) 242 | if Controls["state_"..t].Value == 1 then 243 | Controls["set_"..t]:Trigger() 244 | Controls['out_'..t].Value = 1 245 | Controls['not_'..t].Value = 0 246 | else 247 | Controls["reset_"..t]:Trigger() 248 | Controls['out_'..t].Value = 0 249 | Controls['not_'..t].Value = 1 250 | end 251 | end 252 | 253 | Controls["set_"..t].EventHandler = function(ctrl) 254 | reset(t) 255 | Controls["state_"..t].Value = 1 256 | Controls['out_'..t].Value = 1 257 | Controls['not_'..t].Value = 0 258 | end 259 | 260 | Controls["reset_"..t].EventHandler = function(ctrl) 261 | Controls["state_"..t].Value = 0 262 | Controls['out_'..t].Value = 0 263 | Controls['not_'..t].Value = 1 264 | end 265 | 266 | Controls["toggle_"..t].EventHandler = function(ctrl) 267 | Controls["state_"..t].Value = Controls["state_"..t].Value == 0 268 | Controls["state_"..t].EventHandler() 269 | end 270 | 271 | end 272 | 273 | for t = 1,Properties["Input Count"].Value do 274 | if Controls["state_"..t].Value == 1 then 275 | Controls["set_"..t]:Trigger() 276 | Controls['out_'..t].Value = 1 277 | Controls['not_'..t].Value = 0 278 | else 279 | Controls["reset_"..t]:Trigger() 280 | Controls['out_'..t].Value = 0 281 | Controls['not_'..t].Value = 1 282 | end 283 | 284 | end 285 | end 286 | -------------------------------------------------------------------------------- /Developer/Modules/cpseries_class.lua: -------------------------------------------------------------------------------- 1 | -- ############################################################## 2 | -- CPSeries Class 3 | -- ############################################################## 4 | 5 | function setmeta(table) 6 | local function searchelem(table,index) local m = getmetatable(table) 7 | local list = m.__table or table for count,elem in pairs(list) do 8 | for key,value in pairs(elem) do if elem == table then if index=="index" then return count end 9 | if index=="key" then return key end if index=="value" then return value end end 10 | if key == index then if index~="state" then return elem end return nil end end end 11 | error("variable '"..index.."' is not declared", 2) end 12 | local m = getmetatable(table) or {} setmetatable(table,m) 13 | m.__index = searchelem m = {} m.__index = searchelem m.__table = table 14 | for _,elem in pairs(table) do setmetatable(elem,m) end end 15 | 16 | do 17 | 18 | local POLLTIME = 0.02 19 | local setValue,getValue,setState,getState,Poll,readData, request,received 20 | local privates = setmetatable({}, {__mode = "k"}) -- set 'privates' as private field 21 | 22 | local CP750 ={ { dig_1='digital 1' }, { dig_2='digital 2' }, { dig_3='digital 3' }, { dig_4='digital 4' }, 23 | { analog='Analog Input' },{ non_sync='Non Sync' },{ mic='Microphone' } } 24 | 25 | local Actions = { {fader=false}, {mute=false}, {format=false}, 26 | {formname=false}, {formlist=false}, {reset=false} } 27 | 28 | local SEP = 7 -- - CP650 - - CP750 - - CP850 - 29 | local CPServices ={ {'fader_level', 'cp750.sys.fader', 'sys.fader' }, -- FADER 30 | {'mute', 'cp750.sys.mute', 'sys.mute' }, -- MUTE 31 | {'format_button','cp750.sys.input_mode', 'sys.macro_preset'}, -- FORMAT 32 | { nil, nil, 'sys.macro_name' }, -- FORMNAME 33 | {'format_list', nil, 'sys.macros' }, -- FORMLIST 34 | {'fader_level', 'cp750.sysinfo.version','sys.fader' }, -- QUERY 35 | { '=',' ',' ' } } -- SEP 36 | 37 | -- assign metadata to tables 38 | 39 | setmeta(CP750) 40 | setmeta(Actions) 41 | 42 | --- Creating the Class itself --- 43 | 44 | CPSeries = { EventHandler = nil } 45 | CPSeries.__index = CPSeries 46 | 47 | -- ------------------------------------ 48 | -- public function .New(processorType) 49 | -- param: processor 50 | -- return: object 51 | -- ------------------------------------ 52 | 53 | function CPSeries.New(model) 54 | -- private variables -- 55 | local self = {} 56 | privates[self] = { value={ { fader=0,state=false }, { mute=0,state=false }, { format=0,state=false }, 57 | { formname="",state=false }, { formlist={},state=false }, { reset=false,state=false } }, 58 | time=nil, npoll=0, model=Model.CP850.index, sock=nil, ready=false, cache ={}, waiting=0,tmplist={} } 59 | local private = privates[self] 60 | setmeta(private.value) 61 | for _,elem in pairs(Model) do 62 | if elem.value == model then private.model = elem end 63 | end 64 | private.time = Timer.New() 65 | return setmetatable( self, CPSeries) 66 | end 67 | 68 | 69 | -- ------------------------------------ 70 | -- public function :Start(socket) 71 | -- param: socket connected to processor 72 | -- ------------------------------------ 73 | 74 | function CPSeries:Start(Sock) 75 | local private = privates[self] 76 | private.cache = {} 77 | private.sock = Sock 78 | private.sock.Data = function(sock,data) readData(self) end 79 | private.time.EventHandler = function(timer) Poll(self) end 80 | private.time:Start(POLLTIME) 81 | private.waiting=0 82 | private.npoll = 0 83 | private.ready=false 84 | for _,elem in pairs(Actions) do 85 | setState(self,elem,false) 86 | end 87 | if private.model == Model.CP750 then 88 | local tmplist = {} 89 | for _,v in pairs(CP750) do 90 | table.insert(tmplist,v.value) 91 | end 92 | setValue(self,Actions.formlist,tmplist) 93 | end 94 | request(self,private.model) 95 | end 96 | 97 | -- ------------------------------------ 98 | -- public function :Stop() 99 | -- ------------------------------------ 100 | 101 | function CPSeries:Stop() 102 | local private = privates[self] 103 | private.time:Stop() 104 | end 105 | 106 | -- ------------------------------------ 107 | -- public function :Action( control, value ) 108 | -- param: control, value to send to processor 109 | -- ------------------------------------ 110 | 111 | function CPSeries:Action(control,value) 112 | local private = privates[self] 113 | local action 114 | for _,elem in pairs(Actions) do 115 | if elem.key == control then action = elem end 116 | end 117 | assert(action,"CPSeries:Action -> Invalid control='".. control .."' param") 118 | -- *** FOR DEBUG ONLY *** 119 | --print("Action: control="..action.key.." value=",value) 120 | if not private.ready then return end 121 | if action == Actions.formname then 122 | if private.model ~= Model.CP850 then 123 | action = Actions.format 124 | for t,elem in ipairs(getValue(self,Actions.formlist)) do 125 | if value == elem then value = t break end 126 | end 127 | if self.EventHandler then 128 | -- *** FOR DEBUG ONLY *** 129 | --print("Send Event1",action.key,value) 130 | self.EventHandler(action.key,value) 131 | end end end 132 | if action == Actions.reset then 133 | value = value or true 134 | if self.EventHandler then 135 | self.EventHandler(Actions.formname.key,"") 136 | end 137 | end 138 | setValue(self,action,value,true) 139 | end 140 | 141 | -- ------------ internal local utility functions ----------------- 142 | 143 | local function trimstr(str) if(str) then str = str:match("^(.-)%s*$") end return str end 144 | local function comparetables(t1, t2) if #t1 ~= #t2 then return false end for i=1,#t1 do if t1[i] ~= t2[i] then return false end end return true end 145 | getValue = function(self,action) assert(self,"self is null") assert(action,"action is null") return privates[self].value[action.index][action.key] end 146 | getState = function(self,action) return privates[self].value[action.index]["state"] end 147 | setState = function(self,action,state) privates[self].value[action.index]["state"] = state end 148 | 149 | local function isEqual(a,b) 150 | if type(a)=='table' and type(b) == "table" then 151 | return comparetables( a, b) 152 | elseif type(a)~='table' and type(b) ~= "table" then 153 | return a == b end return false 154 | end 155 | 156 | Print = function(show,...) -- show=false show=true show=nil 157 | local tcp = Properties["TCP Log"] 158 | if Properties.plugin_show_debug.Value == 0 then return end 159 | if ( tcp.Value == 'Command' and show ~= false ) or (tcp.Value == 'All' and show ~= nil) then 160 | print(...) end 161 | end 162 | 163 | local function doClose(self) 164 | local private = privates[self] 165 | if self.EventHandler then 166 | self.EventHandler("close", private.model.value) 167 | end 168 | end 169 | 170 | 171 | local function getButtonName(model,btnNum) 172 | local action = { 173 | [Model.CP650] = function() btnNum = tostring(btnNum - 1) end, --is CP650 174 | [Model.CP750] = function() btnNum = CP750[btnNum].key end, --is CP750 175 | [Model.CP850] = function() btnNum = tostring(btnNum) end } --is CP850 176 | action[model]() 177 | return btnNum 178 | end 179 | 180 | local function getButtonNum(model,btnName) 181 | local function index(btnName) 182 | for _,btn in pairs(CP750) do 183 | if btn.key == btnName then return btn.index end 184 | end end 185 | local action = { 186 | [Model.CP650] = function() btnName = tonumber(btnName)+1 end, --is CP650 187 | [Model.CP750] = function() btnName = index(btnName) end, --is CP750 188 | [Model.CP850] = function() btnName = tonumber(btnName) end } --is CP850 189 | action[model]() 190 | return btnName 191 | end 192 | 193 | setValue = function(self,action,value,isevent) 194 | local private = privates[self] 195 | isevent = isevent or false 196 | assert(action,"setValue->action is null)") 197 | if isEqual(getValue(self,action),value) then 198 | return 199 | end 200 | -- ***FOR DEBUG ONLY!!!**** 201 | --print("setValue","action="..action.key,"value="..tostring(value),"isevent="..tostring(isevent)) 202 | if action == Actions.format and value==0 then 203 | if self.EventHandler then 204 | -- *** FOR DEBUG ONLY *** 205 | --print("Send Event0",Actions.reset.key,getValue(self,Actions.format)) 206 | self.EventHandler(Actions.reset.key,getValue(self,Actions.format)) 207 | end 208 | end 209 | if getState(self,action) == false or isevent then 210 | private.value[action.index][action.key] = value 211 | end 212 | if isevent then 213 | setState(self,action,true) 214 | elseif self.EventHandler and (action~=Actions.format or value >0 ) then 215 | -- *** FOR DEBUG ONLY *** 216 | --print("Send Event3",action.key,getValue(self,action)) 217 | self.EventHandler(action.key,getValue(self,action)) 218 | end 219 | if (action == Actions.format or action == Actions.formname ) 220 | and getValue(self,Actions.reset) == true then 221 | setValue(self,Actions.reset,false,true) 222 | end 223 | if private.model ~= Model.CP850 then 224 | if action == Actions.formlist then 225 | for t,v in ipairs(getValue(self,Actions.formlist)) do 226 | if t == getValue(self,Actions.format) then 227 | setValue(self,Actions.format,t) 228 | end end 229 | elseif action == Actions.format then 230 | local s 231 | for t,v in ipairs(getValue(self,Actions.formlist)) do 232 | if t == getValue(self,Actions.format) then s = v end 233 | end 234 | setValue(self,Actions.formname,s) 235 | if self.EventHandler then 236 | self.EventHandler(Actions.formname.key,s) 237 | end 238 | end 239 | elseif action == Actions.format and self.EventHandler then 240 | self.EventHandler(Actions.formname.key,getValue(self,Actions.formname)) 241 | end end 242 | 243 | local function writeSocket(self,msg,updated) 244 | local private = privates[self] 245 | if not private.sock.IsConnected then doClose(self) end 246 | local function write() 247 | private.sock:Write(msg..'\r\n') 248 | end 249 | pcall(write) -- write to socket 250 | Print(updated,'TX',msg) 251 | end 252 | 253 | request = function(self,model) 254 | local private = privates[self] 255 | local msg = CPServices[Actions.reset.index][model.index] 256 | .. CPServices[SEP][model.index] .. '?' 257 | table.insert(private.cache,msg) 258 | end 259 | 260 | readData = function(self) 261 | local private = privates[self] 262 | local watchdog = false 263 | repeat 264 | local str = trimstr(private.sock:ReadLine(TcpSocket.EOL.Any)); 265 | if str and str ~='' then watchdog = true received(self,str) end 266 | until str==nil or private.sock.IsConnected == false 267 | if not private.sock.IsConnected then doClose(self) end 268 | if (watchdog) then private.waiting = 0 end 269 | end 270 | 271 | local function pollAction(self) 272 | local private = privates[self] 273 | local action = nil 274 | local num = 1 275 | num = private.npoll % 2 == 1 and 2 or num 276 | num = private.npoll % 4 == 3 and 3 or num 277 | num = private.npoll % 8 == 7 and 4 or num 278 | num = private.npoll % 0x2000 == 0 and 5 or num 279 | --if #private.cache == 0 then 280 | private.npoll = ( private.npoll + 1 ) % 0x2000 or 0 281 | --end 282 | for _,elem in pairs(Actions) do 283 | if elem.index == num then action = elem end 284 | end 285 | return action 286 | end 287 | 288 | -- -------------------------------------------- 289 | -- Poll : send TCP Packet to Processor 290 | -- ------------------------------------------- 291 | 292 | Poll = function(self) 293 | local msg 294 | local private = privates[self] 295 | if private.waiting > 30 then 296 | doClose(self) return end 297 | if private.waiting ==0 then 298 | local updated = false 299 | if #private.cache == 0 then 300 | local result = '?' 301 | local action = pollAction(self) 302 | msg = CPServices[action.index][private.model.index] 303 | if msg == nil then return end 304 | msg = msg .. CPServices [SEP][private.model.index] 305 | if ( action ~= Actions.format or getValue(self,Actions.reset)==false ) and 306 | getState(self,action) and action ~= Actions.formlist then 307 | result = getValue(self,action) 308 | if action == Actions.format then 309 | result = getButtonName(private.model,result) 310 | end 311 | if action == Actions.fader then 312 | result = string.format('%.0f',result * 10) 313 | end 314 | if action == Actions.mute then 315 | result = string.format('%.0f',result) 316 | end 317 | updated = getState(self,action) 318 | end 319 | msg = msg .. result 320 | else msg = table.remove(private.cache) end 321 | writeSocket(self, msg, updated ) 322 | end 323 | private.waiting = private.waiting + 1 324 | end 325 | 326 | -- ------------------------------------------- 327 | 328 | -- TCP Packet received from processor 329 | -- ------------------------------------------- 330 | 331 | received = function(self,msg) 332 | local private = privates[self] 333 | local result,pattern,action 334 | Print(false,"RX",string.sub(msg,1,30)) 335 | for t, actiontype in ipairs(CPServices) do 336 | pattern = actiontype[private.model.index] 337 | if pattern ~= nil then 338 | result = string.match(msg,'^'..pattern..CPServices[SEP][private.model.index]..'?(.*)') 339 | if result ~= nil then 340 | for _,elem in pairs(Actions) do 341 | if elem.index == t then action = elem end 342 | end break end end end 343 | if action==nil then -- unreconigzed action 344 | if private.model == Model.CP850 then -- Element List for CP850 345 | result = string.match(msg,'%d+:(.*)') 346 | table.insert(private.tmplist,result) 347 | return 348 | end 349 | Print(true,string.sub(msg,1,30)) 350 | return --ignore action 351 | end 352 | if not private.ready then 353 | if CPServices[action.index][private.model.index] == CPServices[Actions.reset.index][private.model.index] then 354 | private.ready = true 355 | if self.EventHandler then 356 | self.EventHandler('ready',private.model.value) 357 | end 358 | end 359 | return 360 | end 361 | if action == Actions.formlist then 362 | if private.model == Model.CP650 then 363 | local tmp = {} local pos = 1 364 | while pos do 365 | local v = result:match('(%d+)',pos) 366 | if v == nil then break end 367 | table.insert(tmp,'Format '.. v) 368 | pos = result:find(',',pos) 369 | if pos then pos = pos + 1 end 370 | end result = tmp 371 | else -- is CP850 372 | private.tmplist = {} 373 | readData(self,true) 374 | result = private.tmplist 375 | end end 376 | if action == Actions.format then 377 | result = getButtonNum(private.model,result) 378 | setState(self,action,false) 379 | end 380 | if action == Actions.fader or action == Actions.mute then 381 | result = tonumber(string.format('%.f',result)) 382 | if action == Actions.fader then 383 | result = result / 10 384 | end 385 | end 386 | if isEqual(getValue(self,action),result) then 387 | setState(self,action,false) 388 | return 389 | end 390 | Print(nil,"RX",string.sub(msg,1,30)) 391 | setValue(self,action,result) 392 | end 393 | 394 | end -- end do 395 | -------------------------------------------------------------------------------- /DolbyFader.qplug: -------------------------------------------------------------------------------- 1 | -- Dolby Fader for Q-SYS 2 | -- by james.puig@dolby.com 3 | -- Sept '20 4 | 5 | PluginInfo = { 6 | Name = "Dolby~Dolby Fader", 7 | Version = "1.0", 8 | BuildVersion = "1.0.2.1", 9 | Id = "a07f3b34-09af-4c25-af2d-be47d00bb4e9", 10 | Author = "(c) Jaume Puig, Barcelona", 11 | Description = "Dolby Fader Component Plugin", 12 | } 13 | 14 | function GetColor(props) 15 | return { 204, 204, 204 } 16 | end 17 | 18 | function GetPrettyName(props) 19 | return "Dolby Fader " --.. PluginInfo.Version 20 | end 21 | 22 | function GetProperties() 23 | return props 24 | end 25 | 26 | function RectifyProperties(props) 27 | props.plugin_show_debug.IsHidden = true 28 | return props 29 | end 30 | 31 | function GetComponents(props) 32 | local comps = {} 33 | table.insert(comps, 34 | { 35 | Name = "Step", 36 | Type = "stepper", 37 | Properties = 38 | { 39 | control_type = 2, --// Integer 40 | num_steps = 100, 41 | }, 42 | }) 43 | return comps 44 | end 45 | 46 | function GetControls(props) 47 | local ctrls = {} 48 | 49 | table.insert(ctrls, { 50 | Name = "ref", 51 | ControlType = "Button", 52 | ButtonType = "Momentary", 53 | }) 54 | 55 | table.insert(ctrls, { 56 | Name = "level", 57 | ControlType = "Text", 58 | UserPin = true, 59 | PinStyle = "Both", 60 | }) 61 | 62 | table.insert(ctrls, { 63 | Name = "gain", 64 | ControlType = "Knob", 65 | ControlUnit = "dB", 66 | Min = -100, 67 | Max = 20, 68 | PinStyle = "Both", 69 | }) 70 | 71 | table.insert(ctrls, { 72 | Name = "increase", 73 | ControlType = "Button", 74 | ButtonType = "Momentary", 75 | IconType="Icon", 76 | Icon="Plus", 77 | IconColor={0,0,0}, 78 | }) 79 | 80 | table.insert(ctrls, { 81 | Name = "decrease", 82 | ControlType = "Button", 83 | ButtonType = "Momentary", 84 | IconType="Icon", 85 | Icon="Minus", 86 | IconColor={0,0,0}, 87 | }) 88 | return ctrls 89 | end 90 | 91 | function GetControlLayout(props) 92 | local layout = {} 93 | local graphics = {} 94 | local left= -4 95 | local top = -4 96 | 97 | layout['ref'] = { 98 | PrettyName = "Ref Level", 99 | Style = "Button", 100 | ButtonStype = "Momentary", 101 | Color = {242,137,174}, 102 | Size={36,16}, 103 | Position = {left+26,top+48}, 104 | } 105 | layout['level'] = { 106 | PrettyName='Dolby Level', 107 | Style = "Knob", 108 | Color = {0,226,113}, 109 | Position = { left+90, top+28}, 110 | Size={36,36}, 111 | } 112 | layout['increase'] = { 113 | PrettyName = "Increase", 114 | Style = "Button", 115 | ButtonStype = "Momentary", 116 | Color = {255,255,255}, 117 | Position={left+154,top+28}, 118 | Size={36,16}, 119 | } 120 | layout['decrease'] = { 121 | PrettyName = "Decrease", 122 | Style = "Button", 123 | ButtonStype = "Momentary", 124 | Color = {255,255,255}, 125 | Size={36,16}, 126 | Position={left+154,top+48 }, 127 | } 128 | layout["gain"] = { 129 | PrettyName = "Gain", 130 | Style = "Knob", 131 | Position={left+214,top + 28 }, 132 | Size = { 36,36 }, 133 | } 134 | 135 | graphics = { 136 | { 137 | Type="GroupBox", 138 | Text="Knob", 139 | HTextAlign="Left", 140 | StrokeWidth=1, 141 | CornerRadius=8, 142 | Position={left+8,top+8}, 143 | Size={265,100}, 144 | }, 145 | { 146 | Type="Label", 147 | Text="Reference", 148 | Position={left+12,top+72}, 149 | Size={64,16}, 150 | }, 151 | { 152 | Type="Label", 153 | Text="Dolby\nLevel", 154 | Position={left+76, top+72}, 155 | Size={64,32}, 156 | }, 157 | { 158 | Type="Label", 159 | Text="Inc/Dec", 160 | Position={left+140, top + 72}, 161 | Size={64,16}, 162 | }, 163 | { 164 | Type="Label", 165 | Text="RMS Level (dBFS)", 166 | Position={left+202,top+72}, 167 | Size={64,32}, 168 | }, 169 | } 170 | return layout, graphics 171 | end 172 | 173 | if not Controls and Reflect then return end 174 | 175 | --## Code for Dolby Fader 176 | 177 | -------- STRICT ----------------------- 178 | -- Verify strict variables 179 | -- Error if variable is not declared 180 | --------------------------------------- 181 | 182 | do 183 | 184 | local mt = getmetatable(_G) 185 | 186 | if mt == nil then 187 | mt = {} 188 | setmetatable(_G, mt) 189 | end 190 | mt.__declared = {} 191 | 192 | function Global(...) 193 | for _, key in ipairs{...} do 194 | mt.__declared[key] = true 195 | end 196 | end 197 | 198 | mt.__newindex = function (table, key, value) 199 | if not mt.__declared[key] then 200 | if debug.getinfo(2, "S").what ~= "C" then 201 | error("assign to undeclared variable '"..key.."'", 2) 202 | end 203 | mt.__declared[key] = true 204 | end 205 | rawset(table, key, value) 206 | end 207 | 208 | mt.__index = function (table, key) 209 | if not mt.__declared[key] and debug.getinfo(2, "S").what ~= "C" then 210 | error("variable '"..key.."' is not declared", 2) 211 | end 212 | return rawget(table, key) 213 | end 214 | 215 | end 216 | 217 | Global("class","Class") 218 | Global("QKnob") 219 | Global("DKNob","DolbyFaderEventHandler") 220 | 221 | 222 | --################ Class classes ########################## 223 | -- https://github.com/jonstoler/class.lua 224 | --################ Class classes ########################## 225 | 226 | do 227 | 228 | Class = {} 229 | 230 | -- default (empty) constructor 231 | function Class:init(...) end 232 | 233 | -- create a subclass 234 | function Class:extend(obj) 235 | local obj = obj or {} 236 | 237 | local function copyTable(table, destination) 238 | local table = table or {} 239 | local result = destination or {} 240 | for k, v in pairs(table) do 241 | if not result[k] then 242 | if type(v) == "table" and k ~= "__index" and k ~= "__newindex" then 243 | result[k] = copyTable(v) 244 | else 245 | result[k] = v 246 | end 247 | end 248 | end 249 | return result 250 | end 251 | 252 | copyTable(self, obj) 253 | obj._ = obj._ or {} 254 | local mt = {} 255 | 256 | -- create new objects directly, like o = Object() 257 | mt.__call = function(self, ...) 258 | return self:new(...) 259 | end 260 | 261 | -- allow for getters and setters 262 | mt.__index = function(table, key) 263 | local val = rawget(table._, key) 264 | if val and type(val) == "table" and (val.get ~= nil or val.value ~= nil) then 265 | if val.get then 266 | if type(val.get) == "function" then 267 | return val.get(table, val.value) 268 | else 269 | return val.get 270 | end 271 | elseif val.value then 272 | return val.value 273 | end 274 | else 275 | return val 276 | end 277 | end 278 | 279 | mt.__newindex = function(table, key, value) 280 | local val = rawget(table._, key) 281 | if val and type(val) == "table" and ((val.set ~= nil and val._ == nil) or val.value ~= nil) then 282 | local v = value 283 | if val.set then 284 | if type(val.set) == "function" then 285 | v = val.set(table, value, val.value) 286 | else 287 | v = val.set 288 | end 289 | end 290 | val.value = v 291 | if val and val.afterSet then 292 | val.afterSet(table, v) 293 | end 294 | else 295 | table._[key] = value 296 | end 297 | end 298 | 299 | setmetatable(obj, mt) 300 | 301 | return obj 302 | end 303 | 304 | -- set properties outside the constructor or other functions 305 | function Class:set(prop, value) 306 | if not value and type(prop) == "table" then 307 | for k, v in pairs(prop) do 308 | rawset(self._, k, v) 309 | end 310 | else 311 | rawset(self._, prop, value) 312 | end 313 | end 314 | 315 | -- create an instance of an object with constructor parameters 316 | function Class:new(...) 317 | local obj = self:extend({}) 318 | if obj.init then obj:init(...) end 319 | return obj 320 | end 321 | 322 | 323 | function class(attr) 324 | attr = attr or {} 325 | return Class:extend(attr) 326 | end 327 | 328 | end 329 | 330 | 331 | --/////////////////////// QKNob Class ////////////////////////////// 332 | 333 | 334 | do 335 | 336 | -- declare local variables & functions 337 | local setposition,getposition,formatstr,formatval,todigits,compare 338 | local privates = setmetatable({}, {__mode = "k"}) 339 | 340 | QKnob = class() 341 | 342 | QKnob:set("Value",{ 343 | value = 0, 344 | get = function(self,value) return value end, 345 | set = function(self, newVal,oldVal) 346 | newVal = formatval(self,newVal) 347 | privates[self].ctrl.Value = newVal 348 | if not privates[self].nested then 349 | privates[self].nested = true 350 | self.Position = setposition(self,newVal) 351 | self.String = newVal 352 | privates[self].nested = false 353 | end 354 | return newVal 355 | end, 356 | } ) 357 | 358 | QKnob:set("String",{ 359 | value = "", 360 | get = function(self,value) return value end, 361 | set = function(self, newVal,oldVal) 362 | newVal = formatstr(self,newVal) 363 | privates[self].ctrl.String = newVal 364 | if not privates[self].nested then 365 | privates[self].nested = true 366 | self.Value = newVal 367 | self.Position = setposition(self,self.Value) 368 | privates[self].nested = false 369 | end 370 | return newVal 371 | end, 372 | }) 373 | 374 | QKnob:set("Position",{ 375 | value = 0, 376 | get = function(self,value) return value end, 377 | set = function(self, newVal,oldVal) 378 | local val = getposition(self,newVal) 379 | newVal = setposition(self,val) 380 | privates[self].ctrl.Position = newVal 381 | if not privates[self].nested then 382 | privates[self].nested = true 383 | self.Value = getposition(self,newVal) 384 | self.String = self.Value 385 | privates[self].nested = false 386 | end 387 | return newVal 388 | end, 389 | } ) 390 | 391 | 392 | function QKnob:SetString(val) 393 | return val 394 | end 395 | 396 | function QKnob:init(ctlName, Min, Max, numDecs ) 397 | local ctrldef = nil 398 | for _,def in ipairs(GetControls(Properties)) do 399 | if def.Name == ctlName then ctrldef = def break end 400 | end 401 | assert(ctrldef,"GetComponents: "..ctlName.." not found") 402 | assert(ctrldef.ControlType=="Text","GetComponents: "..ctlName..".Type is not 'Text'") 403 | privates[self] = { ctrl = Controls[ctlName], nested = false, min=Min, max=Max, 404 | numdecs = numDecs, tm = Timer.New() } 405 | self.EventHandler = nil 406 | privates[self].ctrl.EventHandler = function() 407 | local newVal = formatstr(self,privates[self].ctrl.String) 408 | if self.String ~=newVal then 409 | self.String = newVal 410 | if self.EventHandler then 411 | self.EventHandler(privates[self].ctrl) 412 | end 413 | end 414 | end 415 | self.String = privates[self].ctrl.String 416 | privates[self].tm.EventHandler = function() 417 | compare(self) 418 | end 419 | privates[self].tm:Start(1/1000) 420 | end 421 | 422 | todigits = function (a) 423 | local _ 424 | a = tostring(a) or "" 425 | a,_ = a:gsub("[^%d.-]", "") 426 | return tonumber(a) or 0 427 | end 428 | 429 | setposition = function(self,value) 430 | local p = (value-privates[self].min)/(privates[self].max-privates[self].min) 431 | return p<0 and 1+p or p 432 | end 433 | 434 | getposition = function(self,value) 435 | value = value * ( privates[self].max-privates[self].min ) + privates[self].min 436 | return value 437 | end 438 | 439 | compare = function(self) 440 | if math.abs(privates[self].ctrl.Position - self.Position)>=1e-3 and not privates[self].nested then 441 | self.Position = privates[self].ctrl.Position 442 | if self.EventHandler then 443 | self.EventHandler(privates[self].ctrl) 444 | end 445 | end 446 | end 447 | 448 | formatval = function(self,val) 449 | val = todigits(val) 450 | val = val < privates[self].min and privates[self].min or val 451 | val = val > privates[self].max and privates[self].max or val 452 | if privates[self].numdecs then 453 | val = tonumber(string.format("%."..privates[self].numdecs.."f",val)) 454 | end 455 | return val 456 | end 457 | 458 | formatstr = function(self,val) 459 | val = todigits(val) 460 | val = val < privates[self].min and privates[self].min or val 461 | val = val > privates[self].max and privates[self].max or val 462 | local neg = val<0 and true or false 463 | val = math.abs(val) 464 | local num = 0 465 | num = val<100 and 1 or num 466 | num = val<10 and 2 or num 467 | num = val< 1 and 3 or num 468 | if privates[self].numdecs then 469 | num = num>privates[self].numdecs and privates[self].numdecs or num 470 | end 471 | val = string.format("%."..num.."f",val) 472 | if not privates[self].numdecs then 473 | val = tonumber(val)<1 and val:sub(2) or val 474 | val = tonumber(val)==0 and 0 or val 475 | end 476 | if neg then val = '-'.. val end 477 | val = self:SetString(tostring(val)) 478 | return tostring(val) 479 | end 480 | 481 | end 482 | 483 | 484 | DKNob = QKnob:new('level',0,10,1) 485 | 486 | local function convertToDb(val) 487 | if val <= 4 then return val*20-90 488 | else return (val-7)*10/3 end 489 | end 490 | 491 | local function convertToDolby(dB) 492 | if dB <= -10 then return (dB+90)/20 493 | else return (dB*3/10)+7 end 494 | end 495 | 496 | Step.value.EventHandler = function(ctrl) 497 | DKNob.Position = Step.value.Value / 100 498 | DKNob.EventHandler(Step) 499 | end 500 | 501 | DKNob.EventHandler = function(ctrl) 502 | if ctrl ~= Controls.gain then 503 | Controls.gain.Value = convertToDb( DKNob.Value ) 504 | end 505 | Step.value.Value = DKNob.Position * 100 506 | if DolbyFaderEventHandler then 507 | DolbyFaderEventHandler(DKNob) 508 | end 509 | end 510 | 511 | Controls.gain.EventHandler = function(ctrl) 512 | DKNob.Value = convertToDolby( Controls.gain.Value) 513 | DKNob.EventHandler(Controls.gain) 514 | end 515 | 516 | Controls.ref.EventHandler = function(ctrl) 517 | if Controls.ref.Value == 1 then 518 | DKNob.Value = 7.0 519 | DKNob.EventHandler(Controls.ref) 520 | end 521 | end 522 | 523 | Controls.increase.EventHandler = function() 524 | Step.increase.Value = Controls.increase.Value 525 | end 526 | 527 | Controls.decrease.EventHandler = function() 528 | Step.decrease.Value = Controls.decrease.Value 529 | end 530 | 531 | -- Execute at StartUp 532 | Controls.gain.EventHandler() 533 | DolbyFaderEventHandler = nil 534 | 535 | 536 | -------------------------------------------------------------------------------- /Dolby Sweep V1.04.qplug: -------------------------------------------------------------------------------- 1 | -- Dolby Sweep Generator Plugin for Q-SYS 2 | -- by James Puig / james.puig@dolby.com 3 | -- Jul '20 4 | 5 | PluginInfo = 6 | { 7 | Name = "Dolby~Dolby Sweep", 8 | Version = "1.0", 9 | BuildVersion = "1.0.4.1", 10 | Id = "0e5ea38f-6c26-469b-aa3b-00503f1f263a", 11 | Description = "Dolby Sweep Generator", 12 | Manufacturer = "Dolby", 13 | Author = "James Puig - james.puig@dolby.com", 14 | Type = Reflect and Reflect.Types.AudioIO or 0, 15 | } 16 | 17 | 18 | 19 | function GetColor(props) 20 | return {164,212,176 } --Is Audio 21 | end 22 | 23 | function GetPrettyName(props) 24 | return "Dolby Sweep Generator" 25 | end 26 | 27 | function GetProperties() 28 | props = {} 29 | return { 30 | 31 | { 32 | Name = "Type", 33 | Type = "enum", 34 | Choices = { "Mono", "Stereo","Multi-channel" }, 35 | Value = "Mono", 36 | }, 37 | { 38 | Name = "Count", 39 | Type = "integer", 40 | Value = 8, 41 | Min = 2, 42 | Max=256, 43 | }, 44 | 45 | } 46 | end 47 | 48 | function RectifyProperties(props) 49 | props.Count.IsHidden = props.Type.Value ~= "Multi-channel" 50 | props.plugin_show_debug.IsHidden = true 51 | return props 52 | end 53 | 54 | function GetComponents(props) 55 | return { { Name = "Sine", Type = "sine" } } 56 | end 57 | 58 | local function getPinsNames(props) 59 | local names = {} 60 | if props.Type.Value == "Multi-channel" then 61 | for num = 1,props.Count.Value do 62 | table.insert(names,string.format("Output Channel %i", num)) end 63 | elseif props.Type.Value == "Stereo" then 64 | table.insert(names,"Output Left") 65 | table.insert(names,"Output Right") 66 | else table.insert(names,"Output") end 67 | return names 68 | end 69 | 70 | function GetPins(props) 71 | local pins = {} 72 | local names = getPinsNames(props) 73 | for _,name in pairs(names) do 74 | table.insert(pins, { Name = name, Direction = "output" } ) 75 | end 76 | return pins 77 | end 78 | 79 | function GetWiring(props) 80 | local wiring = {} 81 | local names = getPinsNames(props) 82 | for _,name in pairs(names) do 83 | table.insert( wiring, { "Sine Output", name } ) 84 | end 85 | return wiring 86 | end 87 | 88 | function GetControls(props) 89 | return { 90 | { 91 | Name = "start", 92 | ControlType = "Button", 93 | ButtonType = "Toggle", 94 | }, 95 | { 96 | Name = "enable", 97 | ControlType = "Button", 98 | ButtonType = "Toggle", 99 | UserPin = true, 100 | PinStyle = "Both", 101 | }, 102 | { 103 | Name = "trigger", 104 | ControlType = "Button", 105 | ButtonType = "Trigger", 106 | UserPin = true, 107 | PinStyle = "Both", 108 | }, 109 | { 110 | Name = "mute", 111 | ControlType = "Button", 112 | ButtonType = "Toggle", 113 | UserPin = true, 114 | PinStyle = "Both", 115 | }, 116 | { 117 | Name = "period", 118 | ControlType = "Text", 119 | }, 120 | { 121 | Name = "frequency", 122 | ControlType = "Knob", 123 | ControlUnit ="Hz", 124 | Min = 10, 125 | Max = 22000, 126 | UserPin = true, 127 | PinStyle = "Output", 128 | }, 129 | { 130 | Name = "level", 131 | ControlType = "Knob", 132 | ControlUnit ="dB", 133 | Min = -100, 134 | Max = 20, 135 | UserPin = true, 136 | PinStyle = "Both", 137 | }, 138 | } 139 | end 140 | 141 | function GetControlLayout(props) 142 | local left1=5 143 | local left2=149 144 | local top =5 145 | local graphics = { 146 | { 147 | Type="GroupBox", 148 | Text="Run", 149 | HTextAlign="Left", 150 | StrokeWidth=1, 151 | CornerRadius=8, 152 | Position={left1,top}, 153 | Size={136,100}, 154 | }, 155 | { 156 | Type="GroupBox", 157 | Text="Sweep", 158 | HTextAlign="Left", 159 | StrokeWidth=1, 160 | CornerRadius=8, 161 | Position={left2,top}, 162 | Size={208+64,100}, 163 | }, 164 | { 165 | Type="Label", 166 | Text="Free-Run", 167 | HTextAlign="Center", 168 | VTextAlign="Center", 169 | StrokeWidth=0, 170 | CornerRadius=0, 171 | Position={ left1+4, top+40+24} , 172 | Size={64,16}, 173 | }, 174 | { 175 | Type="Label", 176 | Text="One-Shot/Sync", 177 | HTextAlign="Center", 178 | VTextAlign="Center", 179 | StrokeWidth=0, 180 | CornerRadius=0, 181 | Position= { left1+4+64, top+40+24 }, 182 | Size={64,32}, 183 | }, 184 | { 185 | Type="Label", 186 | Text="Mute", 187 | HTextAlign="Center", 188 | VTextAlign="Center", 189 | StrokeWidth=0, 190 | CornerRadius=0, 191 | Position= { left2+4, top+40+24 }, 192 | Size={64,16}, 193 | }, 194 | { 195 | Type="Label", 196 | Text="Period", 197 | HTextAlign="Center", 198 | VTextAlign="Center", 199 | StrokeWidth=0, 200 | CornerRadius=0, 201 | Position= { left2+4+64, top+40+24 }, 202 | Size={64,16}, 203 | }, 204 | { 205 | Type="Label", 206 | Text="Frequency", 207 | HTextAlign="Center", 208 | VTextAlign="Center", 209 | StrokeWidth=0, 210 | CornerRadius=0, 211 | Position= { left2+4+128, top+40+24 }, 212 | Size={64,16}, 213 | }, 214 | { 215 | Type="Label", 216 | Text="RMS Level (dBFS)", 217 | HTextAlign="Center", 218 | VTextAlign="Center", 219 | StrokeWidth=0, 220 | CornerRadius=0, 221 | Position= { left2+4+128+64, top+40+24 }, 222 | Size={64,32}, 223 | }, 224 | } 225 | left1 = left1 + 18 226 | left2 = left2 + 18 227 | top = top + 20 228 | local layout = {} 229 | layout["enable"] = { 230 | PrettyName = "Enable", 231 | Style = "Button", 232 | ButtonStyle = "Toggle", 233 | Position = { left1,top+20 }, 234 | Color = { 242, 137, 174 }, 235 | Size = { 36,16 }, 236 | } 237 | layout["trigger"] = { 238 | PrettyName = "Trigger", 239 | Style = "Button", 240 | ButtonStyle = "Trigger", 241 | Position = { left1+64,top+20 }, 242 | Color = { 255, 255, 255 }, 243 | Size = { 36,16 }, 244 | } 245 | layout["mute"] = { 246 | PrettyName = "Mute", 247 | Style = "Button", 248 | ButtonStyle = "Toggle", 249 | Position = { left2,top+20 }, 250 | Color = { 223, 0, 36 }, 251 | Size = { 36,16 }, 252 | } 253 | layout["period"] = { 254 | PrettyName = "Duty Cycle", 255 | Style = "Knob", 256 | Color = { 254, 248, 174 }, 257 | Position = { left2+64,top }, 258 | Size = { 36,36 }, 259 | } 260 | layout["frequency"] = { 261 | PrettyName = "Frequency", 262 | Style = "Knob", 263 | IsReadOnly = true, 264 | Position = { left2+128,top }, 265 | Size = { 36,36 }, 266 | } 267 | layout["level"] = { 268 | PrettyName = "RMS Level (dBFS)", 269 | Style = "Knob", 270 | Position = { left2+128+64,top }, 271 | Size = { 36,36 }, 272 | } 273 | return layout, graphics 274 | end 275 | 276 | if not Controls and Reflect then return end 277 | 278 | 279 | do 280 | 281 | --/////////////////////// QKNob Class ////////////////////////////// 282 | 283 | 284 | --################ Class classes ########################## 285 | -- https://github.com/jonstoler/class.lua 286 | --################ Class classes ########################## 287 | 288 | do 289 | 290 | Class = {} 291 | 292 | -- default (empty) constructor 293 | function Class:init(...) end 294 | 295 | -- create a subclass 296 | function Class:extend(obj) 297 | local obj = obj or {} 298 | 299 | local function copyTable(table, destination) 300 | local table = table or {} 301 | local result = destination or {} 302 | for k, v in pairs(table) do 303 | if not result[k] then 304 | if type(v) == "table" and k ~= "__index" and k ~= "__newindex" then 305 | result[k] = copyTable(v) 306 | else 307 | result[k] = v 308 | end 309 | end 310 | end 311 | return result 312 | end 313 | 314 | copyTable(self, obj) 315 | obj._ = obj._ or {} 316 | local mt = {} 317 | 318 | -- create new objects directly, like o = Object() 319 | mt.__call = function(self, ...) 320 | return self:new(...) 321 | end 322 | 323 | -- allow for getters and setters 324 | mt.__index = function(table, key) 325 | local val = rawget(table._, key) 326 | if val and type(val) == "table" and (val.get ~= nil or val.value ~= nil) then 327 | if val.get then 328 | if type(val.get) == "function" then 329 | return val.get(table, val.value) 330 | else 331 | return val.get 332 | end 333 | elseif val.value then 334 | return val.value 335 | end 336 | else 337 | return val 338 | end 339 | end 340 | 341 | mt.__newindex = function(table, key, value) 342 | local val = rawget(table._, key) 343 | if val and type(val) == "table" and ((val.set ~= nil and val._ == nil) or val.value ~= nil) then 344 | local v = value 345 | if val.set then 346 | if type(val.set) == "function" then 347 | v = val.set(table, value, val.value) 348 | else 349 | v = val.set 350 | end 351 | end 352 | val.value = v 353 | if val and val.afterSet then 354 | val.afterSet(table, v) 355 | end 356 | else 357 | table._[key] = value 358 | end 359 | end 360 | 361 | setmetatable(obj, mt) 362 | 363 | return obj 364 | end 365 | 366 | -- set properties outside the constructor or other functions 367 | function Class:set(prop, value) 368 | if not value and type(prop) == "table" then 369 | for k, v in pairs(prop) do 370 | rawset(self._, k, v) 371 | end 372 | else 373 | rawset(self._, prop, value) 374 | end 375 | end 376 | 377 | -- create an instance of an object with constructor parameters 378 | function Class:new(...) 379 | local obj = self:extend({}) 380 | if obj.init then obj:init(...) end 381 | return obj 382 | end 383 | 384 | 385 | function class(attr) 386 | attr = attr or {} 387 | return Class:extend(attr) 388 | end 389 | 390 | end 391 | 392 | 393 | do 394 | 395 | -- declare local variables & functions 396 | local setposition,getposition,formatstr,formatval,todigits,compare 397 | local privates = setmetatable({}, {__mode = "k"}) 398 | 399 | QKnob = class() 400 | 401 | QKnob:set("Value",{ 402 | value = 0, 403 | get = function(self,value) return value end, 404 | set = function(self, newVal,oldVal) 405 | newVal = formatval(self,newVal) 406 | privates[self].ctrl.Value = newVal 407 | if not privates[self].nested then 408 | privates[self].nested = true 409 | self.Position = setposition(self,newVal) 410 | self.String = newVal 411 | privates[self].nested = false 412 | end 413 | return newVal 414 | end, 415 | } ) 416 | 417 | QKnob:set("String",{ 418 | value = "", 419 | get = function(self,value) return value end, 420 | set = function(self, newVal,oldVal) 421 | newVal = formatstr(self,newVal) 422 | privates[self].ctrl.String = newVal 423 | if not privates[self].nested then 424 | privates[self].nested = true 425 | self.Value = newVal 426 | self.Position = setposition(self,self.Value) 427 | privates[self].nested = false 428 | end 429 | return newVal 430 | end, 431 | }) 432 | 433 | QKnob:set("Position",{ 434 | value = 0, 435 | get = function(self,value) return value end, 436 | set = function(self, newVal,oldVal) 437 | local val = getposition(self,newVal) 438 | newVal = setposition(self,val) 439 | privates[self].ctrl.Position = newVal 440 | if not privates[self].nested then 441 | privates[self].nested = true 442 | self.Value = getposition(self,newVal) 443 | self.String = self.Value 444 | privates[self].nested = false 445 | end 446 | return newVal 447 | end, 448 | } ) 449 | 450 | 451 | function QKnob:SetString(val) 452 | return val 453 | end 454 | 455 | function QKnob:init(ctlName, Min, Max, numDecs ) 456 | local ctrldef = nil 457 | for _,def in ipairs(GetControls(Properties)) do 458 | if def.Name == ctlName then ctrldef = def break end 459 | end 460 | assert(ctrldef,"GetComponents: "..ctlName.." not found") 461 | assert(ctrldef.ControlType=="Text","GetComponents: "..ctlName..".Type is not 'Text'") 462 | privates[self] = { ctrl = Controls[ctlName], nested = false, min=Min, max=Max, 463 | numdecs = numDecs, tm = Timer.New() } 464 | self.EventHandler = nil 465 | privates[self].ctrl.EventHandler = function() 466 | local newVal = formatstr(self,privates[self].ctrl.String) 467 | if self.String ~=newVal then 468 | self.String = newVal 469 | if self.EventHandler then 470 | self.EventHandler(privates[self].ctrl) 471 | end 472 | end 473 | end 474 | self.String = privates[self].ctrl.String 475 | privates[self].tm.EventHandler = function() 476 | compare(self) 477 | end 478 | privates[self].tm:Start(1/1000) 479 | end 480 | 481 | todigits = function (a) 482 | local _ 483 | a = tostring(a) or "" 484 | a,_ = a:gsub("[^%d.-]", "") 485 | return tonumber(a) or 0 486 | end 487 | 488 | setposition = function(self,value) 489 | local p = (value-privates[self].min)/(privates[self].max-privates[self].min) 490 | return p<0 and 1+p or p 491 | end 492 | 493 | getposition = function(self,value) 494 | value = value * ( privates[self].max-privates[self].min ) + privates[self].min 495 | return value 496 | end 497 | 498 | compare = function(self) 499 | if math.abs(privates[self].ctrl.Position - self.Position)>=1e-3 and not privates[self].nested then 500 | self.Position = privates[self].ctrl.Position 501 | if self.EventHandler then 502 | self.EventHandler(privates[self].ctrl) 503 | end 504 | end 505 | end 506 | 507 | formatval = function(self,val) 508 | val = todigits(val) 509 | val = val < privates[self].min and privates[self].min or val 510 | val = val > privates[self].max and privates[self].max or val 511 | if privates[self].numdecs then 512 | val = tonumber(string.format("%."..privates[self].numdecs.."f",val)) 513 | end 514 | return val 515 | end 516 | 517 | formatstr = function(self,val) 518 | val = todigits(val) 519 | val = val < privates[self].min and privates[self].min or val 520 | val = val > privates[self].max and privates[self].max or val 521 | local neg = val<0 and true or false 522 | val = math.abs(val) 523 | local num = 0 524 | num = val<100 and 1 or num 525 | num = val<10 and 2 or num 526 | num = val< 1 and 3 or num 527 | if privates[self].numdecs then 528 | num = num>privates[self].numdecs and privates[self].numdecs or num 529 | end 530 | val = string.format("%."..num.."f",val) 531 | if not privates[self].numdecs then 532 | val = tonumber(val)<1 and val:sub(2) or val 533 | val = tonumber(val)==0 and 0 or val 534 | end 535 | if neg then val = '-'.. val end 536 | val = self:SetString(tostring(val)) 537 | return tostring(val) 538 | end 539 | 540 | end 541 | 542 | 543 | function QKnob:SetString(val) 544 | return val .. 's' 545 | end 546 | 547 | local period = QKnob:new('period',1,8,1) 548 | 549 | -- CONSTANTS 550 | 551 | local emul = System.IsEmulating 552 | 553 | local OCTAVE = 11 554 | --local numloops = emul and OCTAVE*6 or OCTAVE*24 555 | local numloops = emul and 130 or 130 * 4 556 | 557 | local start = Controls.start 558 | local enable = Controls.enable 559 | local trigger =Controls.trigger 560 | local mute =Controls.mute 561 | local frequency = Controls.frequency 562 | local level = Controls.level 563 | 564 | -- Local Variables 565 | local timer = Timer.New() 566 | local running = false 567 | local step = 0 568 | 569 | -- Internal funtions & events 570 | 571 | local function Start() 572 | timer:Start(period.Value/numloops) 573 | Sine.mute.Value = 0 574 | running = true 575 | end 576 | 577 | local function Stop() 578 | timer:Stop() 579 | Sine.mute.Value = 1 580 | Sine.level.Position = 0 581 | running = false 582 | end 583 | 584 | local function initplugin() 585 | if start.Value == 0 then 586 | start.Value = 1 587 | level.Value = -40 588 | period.Value = 4 589 | frequency.Value = 20 590 | end 591 | Sine.level.Value = 0 592 | enable.EventHandler() 593 | end 594 | 595 | timer.EventHandler= function(ctimer) 596 | local freq = 10 * 2^( step * OCTAVE/numloops ) 597 | if freq > 22000 then freq = 22000 end 598 | frequency.Value = freq 599 | Sine.frequency.Value = freq 600 | step = step + 1 601 | if freq == 22000 then 602 | step = 0 603 | if enable.Value == 0 then 604 | Stop() 605 | end 606 | end 607 | end 608 | 609 | -- EVENTS 610 | 611 | enable.EventHandler = function(ctrl) 612 | if enable.Value == 0 then 613 | Stop() 614 | else 615 | trigger.EventHandler(enable) 616 | end 617 | end 618 | 619 | trigger.EventHandler = function(ctrl) 620 | if ctrl ~= enable then 621 | Stop() 622 | step = 0 623 | frequency.Value = 10 624 | Sine.frequency.Value = 10 625 | end 626 | Timer.CallAfter(Start,0.1) 627 | Sine.mute.Value = mute.Value 628 | end 629 | 630 | period.EventHandler = function(ctrl) 631 | if running then 632 | Stop() 633 | Timer.CallAfter(Start,0.1) 634 | Sine.mute.Value = mute.Value 635 | end 636 | end 637 | 638 | mute.EventHandler = function(ctrl) 639 | Sine.mute.Value = mute.Value == 1 or not running 640 | end 641 | 642 | 643 | -- PLUGIN INITIALIZATION 644 | 645 | initplugin() 646 | 647 | end 648 | 649 | -------------------------------------------------------------------------------- /Dolby CPSeries Control V2.2.qplugx: -------------------------------------------------------------------------------- 1 | {"key":"kZTJTleoThbqmCgSXDdVYMsC8VLzhNzs5RtzJJUzVaEl1SlXaLgbbOgCDKGYJZbmD+jZh5COvGS5mE8ZSbgvC5HvF2adw6NpR3hYORzFSS5g9b3AfJBzfD+he6FhHGUPqrbJrBcNsBvwoZCoCQaQOv5lYiXebc9GezdLDmIVzFOBn1sHzonqmhvd9hX8incujpa4L1F+NBb4FEIz9KddVcFTSb4VrtQtMgI2zZSCgIf1kBeq9y06Vgta8QVn5u3vdcMW0c2OV/iE9k7AK6JFGyNBCivpecFZg+OB8hwdrtXeZlrufY34fI2nUJlf7WbNHIhugX87BRVSHmBGb6ikNQ==","iv":"SYw4BLwr4/3aLZHgr0o2JQ==","data":"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"} --------------------------------------------------------------------------------