How to post a CC event?

I’d like a lua script that can track a given parameter that represents a CC value, and post it when it changes.

Example:

-- A Global pointing to our "CC Mapper" MidiModule to be used throughout the script
theMap = this.parent:getMidiModule("CC Mapper")

-- Some visual feedback for the macro to display the value of what ever controller is set to do vibrato in a text field.
defineParameter("Vibrato", nil, 0, 0, 127, 1)

-- Track a parameter that can be updated via macro page control (knob) which is ultimately tied to a mod matrix.  
defineParameter("setVibrato", nil, 0, 0, 127, 1, function() postVib() end)

-- Post the "setVibrato" parameter value as a valid CC event to the engine if it changes.
function postVib()
    mapper = theMap:getParameter("CC131.Source")
    local newEvent = Event(EventType.controller)
    newEvent.controller = mapper
    newEvent.value = setVibrato
    print(newEvent) --Added just for testing purposes to make sure the newEvent is actually being generated properly.
    postEvent(newEvent)
end

-- What the script does with any controller events it processes.
function onController(event)
  postEvent(event)
  mapper = theMap:getParameter("CC131.Source")
  if event.controller == mapper then
    Vibrato = event.value
  end
end

The result of trying this method does drive the display properly, but when I change the setVibrato parameter via Macro, or directly in the parameter list, the script produces this error message:

event={type=controller, ppqPosition=0.000000, controller=2, value=1}
Parameter change callback setVibrato = 1: ModMatrix Meddler.lua, Line 16: Function HALion::postEvent is not available in the Controller context.

Ultimately I’d like to provide some way that users can adjust the pitch depth of this lfo through the macro page if they don’t happen to have handy access to a physical MIDI controller.

Displaying the last value of the controller isn’t a problem, but I can’t seem to find a way to post new values through a macro page.

Ideas?

Hi Brian,

You can use runSync to get around this.

It should let you create and post events from macro page.

-- A Global pointing to our "CC Mapper" MidiModule to be used throughout the script
theMap = this.parent:getMidiModule("CC Mapper")

-- Some visual feedback for the macro to display the value of what ever controller is set to do vibrato in a text field.
defineParameter("Vibrato", nil, 0, 0, 127, 1)

-- Track a parameter that can be updated via macro page control (knob) which is ultimately tied to a mod matrix.  
defineParameter("setVibrato", nil, 0, 0, 127, 1, function() postVib() end)

function postNewControllerEvent()
  postEvent(newEvent)
end

-- Post the "setVibrato" parameter value as a valid CC event to the engine if it changes.
function postVib()
    mapper = theMap:getParameter("CC131.Source")
    newEvent = Event(EventType.controller) -- make this global variable
    newEvent.controller = mapper
    newEvent.value = setVibrato
    print(newEvent) --Added just for testing purposes to make sure the newEvent is actually being generated properly.
    --postEvent(newEvent)
    runSync(postNewControllerEvent, 1)
end

-- What the script does with any controller events it processes.
function onController(event)
  postEvent(event)
  mapper = theMap:getParameter("CC131.Source")
  if event.controller == mapper then
    Vibrato = event.value
  end
end

Or another way, using controlChange() instead of creating and posting new events. Should do the same thing and it’s a bit shorter.

-- A Global pointing to our "CC Mapper" MidiModule to be used throughout the script
theMap = this.parent:getMidiModule("CC Mapper")

-- Some visual feedback for the macro to display the value of what ever controller is set to do vibrato in a text field.
defineParameter("Vibrato", nil, 0, 0, 127, 1)

-- Track a parameter that can be updated via macro page control (knob) which is ultimately tied to a mod matrix.  
defineParameter("setVibrato", nil, 0, 0, 127, 1, function() postVib() end)

function postNewControllerEvent()
  controlChange(mapper, setVibrato)
end

-- Post the "setVibrato" parameter value as a valid CC event to the engine if it changes.
function postVib()
    mapper = theMap:getParameter("CC131.Source")    
    runSync(postNewControllerEvent, 1)
end

-- What the script does with any controller events it processes.
function onController(event)
  postEvent(event)
  mapper = theMap:getParameter("CC131.Source")
  if event.controller == mapper then
    Vibrato = event.value
  end
end

I’m not sure I completely understand what you’re trying to do but the error message was gone when using runSync.

Thanks!

Either method you offered does the trick. I’ve gone with the second option and found a way to eliminate having to call up the entire midiModule and check that CC131.Source every time a CC comes through postVib() or onController(). It should be more efficient now. I think I can also do it with one parameter “Vibrato”, and can drop the “setVibrato” parameter (I thought I needed it to avoid a ‘double fire’ situation…but apparently not…will test the idea later).

Here’s what I’m looking at so far that seems to be working:

-- Grab the name for Macros
pN = this.parent.name
defineParameter("pName", nil, 0, {pN})

-- A Global pointing to our MidiModule to be used throughtout the script
defineParameter("vibCC", nil, 2, 0, 146, 1, function() vibMap() end)
theMap = this.parent:getMidiModule("CC Mapper")
ctrlB = "CC131.Source"
vibCC = theMap:getParameter(ctrlB)
function vibMap()
  theMap:setParameter(ctrlB, vibCC)
end

-- Some visual feedback for the macro to display what ever controller is set to do vibrato.
defineParameter("Vibrato", nil, 0, 0, 127, 1)

-- Track a parameter that can be updated via macro page which is tied to a mod matrix.  Post it to the engine if it changes.
defineParameter("setVibrato", nil, 0, 0, 127, 1, function() postVib() end)

-- Post the "setVibrato" parameter value as a valid CC event to the engine if it changes.
function postNewControllerEvent()
  controlChange(vibCC, setVibrato)
  Vibrato = setVibrato
end
function postVib() 
    runSync(postNewControllerEvent, 1)
end

-- delay midi cc68 events by delay parameter
defineParameter("legatoDelay", nil, 100, 0, 1000) -- delay in ms, numbers are default, min, max
defineParameter("legatoDisplay", nil, 0, {"Normal", "Legato"})

function onController(event)
  if event.controller == 68 then
    wait(legatoDelay)
    postEvent(event)
    if event.value > 64 then
	legatoDisplay = 2
    else
	legatoDisplay = 1
    end
  else
    postEvent(event)
  end

  if event.controller == vibCC then
    Vibrato = event.value
    setVibrato = event.value
  end
end

The only reason I wanted this is because I’m trying to work with a modulation matrix parameter that as far as I know cannot be directly linked as a parameter from a macro page. Much of the time the user can just move a real fader or something on his controller directly, but in this case I wanted a back-up option on the macro page in case the user wants to try it out and doesn’t have a controller that sends that CC immediately available. The reason being, this is most likely going to be used in Dorico over some oddball CC such as 2breath or 4foot. It’s not always a handy CC for everyone’s keyboard. Having the ability to tweak it there and listen before locking it in on a CC lane or in an expression map could come in handy.

I realize it’s possible to do it through a quick control instead, but that set up some equally complex/confusing issues in the ultimate design of my macro editors. Doing it this way, and going through a CC Mapper, the user can pick a CC from a simple pop-up menu. Doing it via Quick Control requires the user to right click to ‘forget and learn’ things if they want to use a different CC than the default to automate vibrato pitch depth.

It’s also possible to move expression volume off the mod wheel to cc11, and put the vibrato lfo control on cc1…which most every keyboard has and leave this convenience off the table. Still…might as well make it ‘flexible’ so the user can choose. Also, it’s not uncommon for people to work with score editors having no MIDI keyboard connected at all…especially when they hit the road with a laptop.

Also, this ability to generate a CC from a macro page could come in handy for the future as well.

Now I know there is a way to generate and send an intermittent CC and the layers/zones on down the pipe-line do respond to it.

I also notice that events posted via this runSync method don’t seem to get picked up by the same script’s onController() process, but that’s ok. It might get picked up by onUnhandled() events if I need it in the future, but I haven’t had a chance to check that yet.

The only reason I wanted this is because I’m trying to work with a modulation matrix parameter that as far as I know cannot be directly linked as a parameter from a macro page.

In that case you could also look into defineModulation and calcModulation.
You can try something like this:

defineParameter("Vibrato", nil, 0, 0, 127, 1)
defineParameter("CC", nil, 0, 0, 127, 1)

defineModulation("VibratoMod", false)

function calcModulation()
  return Vibrato / 127
end

function onController(event)
  if event.controller == CC then
    Vibrato = event.value
  else
    postEvent(event)
  end
end

The Vibrato parameter is used as a modulation source.

I also notice that events posted via this runSync method don’t seem to get picked up by the same script’s onController() process, but that’s ok. It might get picked up by onUnhandled() events if I need it in the future, but I haven’t had a chance to check that yet.

Yes, you’re right. Midi events created by script module itself are not picked up by onNote, onController… of the same script module. It only picks up midi events coming from outside.

But if you had two Lua Script modules then the second script module would pick up events created by the first module.