User Script Examples - II

User Script Examples - Virtual instruments - Steinberg Forums

It looks like it’s not possible to post in the original thread anymore. Also some attachments didn’t make it to new forum. As I had some users asking for those I decided to post them here.

Here are some midi module presets from the linked topic. Also some new additions. Mostly inspired and adapted from Falcon scripts.

MIDI Modules.zip (36.8 KB)

Some of the new lua script modules:

Chorder

for i=1,6 do
    defineParameter("Shift"..i, nil, 0, -36, 36, 1)	
end
for i=1,6 do
    defineParameter("Velocity"..i, nil, 1, 0.01, 2)	
end

local ids = {}

defineParameter("Mono", nil, false)

function onNote(e)
    local done = {} -- store already played notes in order to avoid redundancy
    if Mono then
        -- release previous chord
        while ids[1] do
            local id = table.remove(ids)
            releaseVoice(id)
        end
    end  
    for i = 1, 6 do
        local shift = _G["Shift"..i]
        local velocity = _G["Velocity"..i]
        if not done[shift] then
            local id = playNote(e.note + shift, math.min(127, e.velocity * velocity))
            done[shift] = true
            if Mono then
                table.insert(ids, id)
            end
        end
    end
end

function onRelease()
    -- eat event
end

Polyphonic Sequencer

local resolutions = {16., 8., 4., 2., 1.5, 4./3., 1., 0.75, 2./3., 0.5, 0.375, 1./3., 0.25, 0.1875, 0.5/3., 0.125}
local resolutionNames = {"4", "2", "1", "1/2", "1/4d", "1/2t", "1/4", "1/8d", "1/4t", "1/8", "1/16d", "1/8t", "1/16", "1/32d", "1/16t", "1/32"}

defineParameter("timeGlobal", nil, 13, resolutionNames)
defineParameter("repeats", nil, 16, 1, 32, 1)
defineParameter("lengthGlobal", nil, 100, 0, 150)
defineParameter("oneShot", nil, false)
defineParameter("randomStart", nil, false)

-------------------------------------------- Scales --------------------------------------------
local KEY = {"C", "Db", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"}

defineParameter("rootKey", nil, 1, KEY)

scales = {
    {"--", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
    {"Major (Ionian)", {0, 0, 2, 2, 4, 5, 5, 7, 7, 9, 9, 11}},
    {"Minor (Aeolian)", {0, 0, 2, 3, 3, 5, 5, 7, 8, 8, 10, 10}},
    {"Harmonic Minor", {0, 0, 2, 3, 3, 5, 5, 7, 8, 8, 11, 11}},
    {"Melodic Minor", {0, 0, 2, 3, 3, 5, 5, 7, 7, 9, 9, 11}},
    {"Major Pentatonic", {0, 0, 2, 2, 4, 4, 7, 7, 7, 9, 9, 12}},
    {"Minor Pentatonic", {0, 0, 3, 3, 3, 5, 5, 7, 7, 10, 10, 10}},
    {"Dorian (D)", {0, 0, 2, 3, 3, 5, 5, 7, 7, 9, 10, 10}},
    {"Phrygian (E)", {0, 1, 1, 3, 3, 5, 5, 7, 8, 8, 10, 10}},
    {"Lydian (F)", {0, 0, 2, 2, 4, 4, 6, 7, 7, 9, 9, 11}},
    {"Mixolydian (G)", {0, 0, 2, 2, 4, 5, 5, 7, 7, 9, 10, 10}},
    {"Locrian (B)", {0, 1, 1, 3, 3, 5, 6, 6, 8, 8, 10, 10}},
    {"Whole Tone", {0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10}},
    {"1/2 Tone 1 Tone", {0, 1, 1, 3, 4, 4, 6, 7, 7, 9, 10, 10}},
    {"1 Tone 1/2 Tone", {0, 0, 2, 3, 3, 5, 6, 6, 8, 9, 9, 11}},
    {"Altered", {0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10}},
    {"Hungarian", {0, 0, 2, 3, 3, 6, 6, 7, 8, 8, 10, 10}},
    {"Phrygish", {0, 1, 1, 4, 4, 5, 5, 7, 8, 8, 10, 10}},
    {"Arabic", {0, 1, 1, 4, 4, 5, 5, 7, 8, 8, 11, 11}},
    {"Persian", {0, 1, 1, 4, 4, 5, 5, 7, 7, 10, 10, 11}},
    {"Acoustic (Lydian b7)", {0, 0, 2, 2, 4, 4, 6, 7, 7, 9, 10, 10}},
    {"Harmonic Major", {0, 0, 2, 2, 4, 5, 5, 7, 8, 8, 11, 11}}
}

scaleNames = {}
for i = 1, #scales do
    scaleNames[i] = scales[i][1]
end

defineParameter("scaleMenu", nil, 1, scaleNames)

function getScaledNote(note)
    local degree = (note - (rootKey - 1) + 12) % 12
    return note + scales[scaleMenu][2][degree + 1] - degree
end

defineParameter("paramSelected", nil, 1, {"Tune", "Velocity", "Pan", "Repeat", "Length"})
for i = 1, 32 do
    defineParameter("Tune"..i, nil, 0, -24, 24, 1)
    defineParameter("Velocity"..i, nil, 1, 0, 2)
    defineParameter("Pan"..i, nil, 0, -1, 1)
    defineParameter("Repeat"..i, nil, 1, 1, 8, 1)
    defineParameter("Length"..i, nil, 1, 0, 1)
end

-------------------------------------------- Playing events
local pedalDown = false

function onNote(e)
    local pos = 0
    local count = 0
    if randomStart == true then
        pos = math.random(0, repeats - 1)
    end
    while (oneShot == false and (isNoteHeld() or pedalDown)) or (oneShot and count < repeats) do
        local time = resolutions[timeGlobal]
        local pos1 = pos + 1
        local vel = _G["Velocity"..pos1]
        local length = lengthGlobal / 100 * _G["Length"..pos1]
        if vel > 0 and length > 0 then
            local pan =  _G["Pan"..pos1]
            local note = getScaledNote(e.note + _G["Tune"..pos1])
            local rep = _G["Repeat"..pos1]
            local repLength = time / rep
            for i = 1, rep do
                playNote(note, math.min(e.velocity * vel, 127), beat2ms(repLength * length), nil, 1, 0 + pan, e.tune)
                waitBeat(repLength)
            end
        else
            waitBeat(time)
        end
        pos = (pos + 1) % repeats
        count = count + 1
    end
end

function onRelease(e)
end

function onController(e)    
    if e.controller == 64 then
        if pedalDown and e.value < 64 then
            pedalDown = false
        elseif not pedalDown and e.value >=64 then
            pedalDown = true
        end 
    else
        postEvent(e)
    end
end

Scale

local KEY = {"C", "Db", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"}
local keyButtonText = {}

for i = 1, 12 do
    defineParameter{name = "KeyButton"..i, default = false, readOnly = true, persistent = false}
end

defineParameter("KeyButtonText", nil, 1, KEY)

rootKeyChanged = function()
    for i = 1, 12 do
        keyButtonText[i] = KEY[(i + rootKey - 2) % 12 + 1]
    end
    defineParameter("KeyButtonText", nil, 1, keyButtonText)
end

defineParameter("rootKey", nil, 1, KEY, rootKeyChanged)

rootKeyChanged()

scales = {
    {"--", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
    {"Major (Ionian)", {0, 2, 4, 5, 7, 9, 11}},
    {"Minor (Aeolian)", {0, 2, 3, 5, 7, 8, 10}},
    {"Harmonic Minor", {0, 2, 3, 5, 7, 8, 11}},
    {"Melodic Minor", {0, 2, 3, 5, 7, 9, 11}},
    {"Major Pentatonic", {0, 2, 4, 7, 9}},
    {"Minor Pentatonic", {0, 3, 5, 7, 10}},
    {"Dorian (D)", {0, 2, 3, 5, 7, 9, 10}},
    {"Phrygian (E)", {0, 1, 3, 5, 7, 8, 10}},
    {"Lydian (F)", {0, 2, 4, 6, 7, 9, 11}},
    {"Mixolydian (G)", {0, 2, 4, 5, 7, 9, 10}},
    {"Locrian (B)", {0, 1, 3, 5, 6, 8, 10}},
    {"Whole Tone", {0, 2, 4, 6, 8, 10}},
    {"1/2 Tone 1 Tone", {0, 1, 3, 4, 6, 7, 9, 10}},
    {"1 Tone 1/2 Tone", {0, 2, 3, 5, 6, 8, 9, 11}},
    {"Altered", {0, 2, 4, 6, 8, 10, 10}},
    {"Hungarian", {0, 2, 3, 6, 7, 8, 10}},
    {"Phrygish", {0, 1, 4, 5, 7, 8, 10}},
    {"Arabic", {0, 1, 4, 5, 7, 8, 11}},
    {"Persian", {0, 1, 4, 5, 7, 10, 11}},
    {"Acoustic (Lydian b7)", {0, 2, 4, 6, 7, 9, 10}},
    {"Harmonic Major", {0, 2, 4, 5, 7, 8, 11}},
}
scaleNames = {}
for i = 1, #scales do
    scaleNames[i] = scales[i][1]
end

function loadScale(scale)
    for i = 1, 12 do
        _G["KeyButton"..i] = false
    end
    for i = 1, #scale do
        _G["KeyButton"..scale[i] + 1] = true
    end
end


scaleMenuChanged = function()
    loadScale(scales[scaleMenu][2])
end

defineParameter{name = "scaleMenu", default = 1, strings = scaleNames, persistent = false, onChanged = scaleMenuChanged}

scaleMenuChanged()

function priotityChanged()
    priorityValue = 2 * Priority - 3
end

defineParameter("Priority", nil, 1, {"Down", "Up"}, priotityChanged)

priotityChanged()

function getScaledNote(note)
    degree = (note - (rootKey - 1) + 12) % 12
    degreeDeviation = 0
    while (true) do
        if (_G["KeyButton"..(degree + degreeDeviation + 12) % 12 + 1]) then break end
        degreeDeviation = - degreeDeviation
        if (_G["KeyButton"..(degree + degreeDeviation + 12) % 12 + 1]) then break end
        degreeDeviation = priorityValue - degreeDeviation
        if (degreeDeviation == priorityValue * 7) then -- no button
            degreeDeviation = 0
            break
        end
    end
    return note + degreeDeviation
end

function onNote(e)
    playNote(getScaledNote(e.note), e.velocity)
end

function onRelease(e)
end

Step Line


-- numSteps
defineParameter("PhraserNumSteps", nil, 16, 1, 16, 1)

-- resolution
local PhraserResolutions = {2., 1.5, 4. / 3., 1., 0.75, 2. / 3., 0.5, 0.375, 1. / 3., 0.25, 0.1875, 0.5 / 3., 0.125}
local resolutionNames = {"1/2", "1/4d", "1/2t", "1/4", "1/8d", "1/4t", "1/8", "1/16d", "1/8t", "1/16", "1/32d", "1/16t", "1/32"}

defineParameter("PhraserResolution", nil, 10, resolutionNames)

-- gate
defineParameter("PhraserGate", nil, 1, 0.001, 1.1)

-- groove
defineParameter("PhraserGroove", nil, 0, 0, 0.5)

-- scales
local KEY = {"C", "Db", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"}
defineParameter("rootKey", nil, 1, KEY)

scales = {
    {"--", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}},
    {"Major (Ionian)", {0, 0, 2, 2, 4, 5, 5, 7, 7, 9, 9, 11}},
    {"Minor (Aeolian)", {0, 0, 2, 3, 3, 5, 5, 7, 8, 8, 10, 10}},
    {"Harmonic Minor", {0, 0, 2, 3, 3, 5, 5, 7, 8, 8, 11, 11}},
    {"Melodic Minor", {0, 0, 2, 3, 3, 5, 5, 7, 7, 9, 9, 11}},
    {"Major Pentatonic", {0, 0, 2, 2, 4, 4, 7, 7, 7, 9, 9, 12}},
    {"Minor Pentatonic", {0, 0, 3, 3, 3, 5, 5, 7, 7, 10, 10, 10}},
    {"Dorian (D)", {0, 0, 2, 3, 3, 5, 5, 7, 7, 9, 10, 10}},
    {"Phrygian (E)", {0, 1, 1, 3, 3, 5, 5, 7, 8, 8, 10, 10}},
    {"Lydian (F)", {0, 0, 2, 2, 4, 4, 6, 7, 7, 9, 9, 11}},
    {"Mixolydian (G)", {0, 0, 2, 2, 4, 5, 5, 7, 7, 9, 10, 10}},
    {"Locrian (B)", {0, 1, 1, 3, 3, 5, 6, 6, 8, 8, 10, 10}},
    {"Whole Tone", {0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10}},
    {"1/2 Tone 1 Tone", {0, 1, 1, 3, 4, 4, 6, 7, 7, 9, 10, 10}},
    {"1 Tone 1/2 Tone", {0, 0, 2, 3, 3, 5, 6, 6, 8, 9, 9, 11}},
    {"Altered", {0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10}},
    {"Hungarian", {0, 0, 2, 3, 3, 6, 6, 7, 8, 8, 10, 10}},
    {"Phrygish", {0, 1, 1, 4, 4, 5, 5, 7, 8, 8, 10, 10}},
    {"Arabic", {0, 1, 1, 4, 4, 5, 5, 7, 8, 8, 11, 11}},
    {"Persian", {0, 1, 1, 4, 4, 5, 5, 7, 7, 10, 10, 11}},
    {"Acoustic (Lydian b7)", {0, 0, 2, 2, 4, 4, 6, 7, 7, 9, 10, 10}},
    {"Harmonic Major", {0, 0, 2, 2, 4, 5, 5, 7, 8, 8, 11, 11}}}

scaleNames = {}
for i = 1, #scales do
    scaleNames[i] = scales[i][1]
end

defineParameter("scaleMenu", nil, 1, scaleNames)

function getScaledNote(note)
    local degree = (note - (rootKey - 1) + 12) % 12
    return note + scales[scaleMenu][2][degree + 1] - degree
end

-- Step Panel
for i = 1, 16 do
    defineParameter("Step"..i, nil, 100, 0, 127, 1)
    defineParameter("Pitch"..i, nil, 0, -48, 48, 1)
    defineParameter("Length"..i, nil, 1, 0.001, 1.1)
    defineParameter("Enable"..i, nil, false)
    defineParameter("Link"..i, nil, false)
    defineParameter("CC1__"..i, nil, 0, 0, 127, 1)
    defineParameter("CC2__"..i, nil, 0, 0, 127, 1)
end

local recordOrigin = nil

-- record
PhraserRecordChanged = function()
    if PhraserRecord then
        recordOrigin = nil
        lastRecordedIndex = 1
    else
        for i = lastRecordedIndex, 16 do
            _G["Pitch"..i] = 0
            _G["Step"..i] = 0      
        end
        for i = 1, 16 do
            _G["Link"..i] = false     
        end
    end
end

defineParameter("PhraserRecord", nil, false, PhraserRecordChanged)

-- skip
PhraserSkipChanged = function()
    if PhraserRecord then
        _G["Pitch"..lastRecordedIndex] = 0
        _G["Step"..lastRecordedIndex] = 0
        lastRecordedIndex = lastRecordedIndex + 1
        if lastRecordedIndex == 17 then
            PhraserRecord = false
        end
    end
    PhraserSkip = false
end

defineParameter("PhraserSkip", nil, false, PhraserSkipChanged)

function randomizePattern()
    PhraserNumSteps = math.floor(math.random(1, 16) + .5)
    for i = 1, PhraserNumSteps do
        _G["Pitch"..i] = math.floor(math.random(-12, 12) + .5)
        _G["Step"..i] = math.floor(math.random(0, 127) + .5)
        _G["Link"..i] = false
    end
    for i = PhraserNumSteps + 1, 16 do
        _G["Pitch"..i] = 0
        _G["Step"..i] = 0
        _G["Link"..i] = false
    end
end

-- Random
PhraserRandomChanged = function()
    if PhraserRandom then
        randomizePattern()
        PhraserRandom = false
    end
end

defineParameter("PhraserRandom", nil, false, PhraserRandomChanged)

-- seq selection button
local PhraserSeqDisplayItems = {"Vel", "Ptch", "Len", "CC1", "CC2"}

defineParameter("PhraserSeqDisplay", nil, 1, PhraserSeqDisplayItems) -- maybe non persistent

function clearPosition()
    for i = 1, 16 do
        _G["Enable"..i] = false
    end
end

local arpId = 0
local heldNotes = {}
local pedalDown = false

function grooveDelay(beatPos, grooveAmount)
    local semi = beatPos * 4 -- convert to 16th pos
    local x = math.fmod(semi, 2.0)
    if x < 1 then
        return 0.25 * grooveAmount * x
    else
        return 0.25 * grooveAmount * (2 - x)
    end
end

function arpeg(arpId_)
    local index = 0
    local theoricalPosInBeat = 0 -- non grooved beat pos
    local beatTime = 0
    while arpId_ == arpId do
        local e = heldNotes[#heldNotes]
        local p = PhraserResolutions[PhraserResolution]
        local numSteps = PhraserNumSteps
        local stepSize = 1
        for i = 1, 16 do -- check for linked
            local pos = (index + i) % numSteps
            local pos1 = pos + 1
            if _G["Link"..pos1] == true then
                stepSize = stepSize + 1
            else
                break
            end
        end

        local index1 = index + 1
        local tune = _G["Pitch"..index1]
        tune = getScaledNote(e.note + tune)
        tune = tune - e.note
        local velocity = _G["Step"..index1]

        local nextTheoricalPosInBeat = theoricalPosInBeat + p
        local beatDelay = grooveDelay(nextTheoricalPosInBeat, PhraserGroove)

        if _G["Link"..index1] == false then
            for i = 1, numCC do
                controlChange(i, _G["CC"..i.."__"..index1])
            end
        end

        if velocity ~= 0 and _G["Link"..index1] == false then
            playNote(e.note, velocity, beat2ms(_G["Length"..index1] * PhraserGate * p * stepSize + beatDelay), nil, 1, 0, tune)
            print(e.note, "    ", e.note + tune)
        end

        _G["Enable"..((index - 1 + numSteps) % numSteps + 1)] = false
        _G["Enable"..((index % numSteps) + 1)] = true

        index = (index + 1) % numSteps
        local timeToWait = nextTheoricalPosInBeat + beatDelay - beatTime
        waitBeat(timeToWait)
        theoricalPosInBeat = nextTheoricalPosInBeat
        beatTime = beatTime + timeToWait
    end
end

function onNote(e)
    table.insert(heldNotes, e)
    if PhraserRecord == true then
        if recordOrigin == nil then
            recordOrigin = e.note
        end
        _G["Pitch"..lastRecordedIndex] = e.note - recordOrigin
        _G["Step"..lastRecordedIndex] =  e.velocity
        PhraserNumSteps = lastRecordedIndex
        lastRecordedIndex = lastRecordedIndex + 1
        if lastRecordedIndex == 17 then
            PhraserRecord = false
        end
        playNote(e.note, e.velocity -1)
    else
        if #heldNotes == 1 then
            arpeg(arpId)
        end
    end
end

function onRelease(e)
    if pedalDown == true then
        return
    end
    for i, v in ipairs(heldNotes) do
        if v.note == e.note then
            table.remove(heldNotes, i)
            if #heldNotes == 0 then
                clearPosition()
                arpId = arpId + 1
            end
            break
        end
    end
end

function pedalChanged()
    for i=#heldNotes,1,-1 do
        if not isKeyDown(heldNotes[i].note) then
            table.remove(heldNotes, i)
        end
    end
    if #heldNotes == 0 then
        clearPosition()
        arpId = arpId + 1
    end
end

function onController(e)
    if e.controller == 64 then
        if pedalDown and e.value < 64 then
            pedalDown = false
            pedalChanged()
        elseif not pedalDown and e.value >= 64 then
            pedalDown = true
        end
    else
        postEvent(e)
    end
end

All of these are included in the attachment as midi module presets for HALion 6. They have a simple macro page attached to the script module.

2 Likes

Hi.

What about creating a sticky topic on this forum where users would post just the code of their script and a short description of what it does and how to use it.

Together with the examples provided by Steinberg it would be a great help for people just starting to learn scripting.

1 Like

Not a bad idea at all.

+1

I see my thread came alive just when I was thinking about deleting the topic. :smiley:

So I’ll give it a go with some of my “utility” scripts.

If you want to try them but you get a syntax error when trying to copy and paste the code into Halion download the attachment. I’ve included these examples as both lua script files and Halion MIDI Module presets (plus two more midi module presets with a simple macro page).
Halion.zip (17 KB)
Quick Control Setup.

Tries to assign Filter Cutoff and Resonance, Amp Attack and Release (Offsets) and some of the parameters of Reverb, Delay and Modulation effects.
It does remove any already existing quick control assignments of the program.

program=this.program
zones=this.parent:findZones(true)
effects=program:findEffects(true)

function removeAllQCAssignments(layer)
  for i=1,11 do
    local assignment=layer:getNumQCAssignments(i)
    if assignment>0 then
      for j=assignment,1,-1 do
        layer:removeQCAssignment(i,j)
      end
    end
    layer:setParameter("QuickControl.QC"..i,50)
  end
end

removeAllQCAssignments(program)

function trimRangeQC(layer,qc,index,paramNorm)
  layer:setParameterNormalized("QuickControl.QC"..qc,paramNorm)
  layer:setQCAssignmentMin(qc,index,50-paramNorm*100/2)
  layer:setQCAssignmentMax(qc,index,50+(1-paramNorm)*100/2)
end

function assignQC(layer,qc,element,param,scope,qcName)
  layer:addQCAssignment(qc,element,param,scope)
  layer:setParameter("QuickControl.Name"..qc,qcName)
  local paramNorm=element:getParameterNormalized(param)
  local assignment=layer:getNumQCAssignments(qc)
  trimRangeQC(layer,qc,assignment,paramNorm)
end

function assignSphere()
  assignQC(program,9,zones[1],"CutoffModOffset",program,"Cutoff Offset")
  assignQC(program,10,zones[1],"ResoModOffset",program,"Resonance Offset")
  print("Sphere assigned to Filter Cutoff and Resonance Offset")
end





paramNames={
  {parameter="Filter.Cutoff",     name="Filter Cutoff"},
  {parameter="Filter.Resonance",  name="Resonance"},
  {parameter="DCAAttOffset",      name="Attack"},
  {parameter="DCARelOffset",      name="Release"}
  }


if zones[1] then
  for i=1,4 do
    assignQC(program,i,zones[1],paramNames[i].parameter,program,paramNames[i].name)
    print("QC"..i..": "..paramNames[i].name)
  end
  for i,zone in ipairs(zones) do
    if zone:getParameter("Filter.Type")==0 then
      zone:setParameter("Filter.Type",1)
    end
    if zone:getParameter("ZoneType")==3 then
      print("Filter Cutoff and Resonance have no effect on Organ Zones")
    end
  end
  assignSphere()
else
  print("No Zones Found")
end


effectParameters={
  ["Reverb"]=                 {par={"Predelay","Mix"},          nam={"Predelay","Mix"}},
  ["REVerence"]=              {par={"predelay","mix"},          nam={"Predelay","Mix"}},
  ["Chorus"]=                 {par={"Rate","Depth"},            nam={"Rate","Depth"}},
  ["Flanger"]=                {par={"Rate","Depth"},            nam={"Rate","Depth"}},
  ["Phaser"]=                 {par={"rate","mix"},              nam={"Rate","Mix"}},
  ["Multi Delay"]=            {par={"feedbackoverall","Mix"},   nam={"Feedback","Mix"}},
  ["Studio EQ"]=              {par={"gainlfl","gainhfl"},       nam={"Gain Low","Gain High"}},
  ["Resonator"]=              {par={"CutoffSpread","Mix"},      nam={"Cutoff Spread","Mix"}},
  ["Ring Modulator"]=         {par={"Freq","Mix"},              nam={"Frequency","Mix"}},
  ["Frequency Shifter"]=      {par={"ModCoarse","Mix"},         nam={"Mod Coarse","Mix"}},
  ["Compressor"]=             {par={"ratio","threshold"},       nam={"Ratio","Threshold"}},
}




qc=5
k=0
if effects[1] then
  for i,effect in ipairs(effects) do
    local name=effect.name
    if effectParameters[name] then
      k=k+1
    end
  end
  if k<=2 then
    for i,effect in ipairs(effects) do
      local name=effect.name
      if effectParameters[name] then
       for j=1,2 do
         local parameter=effectParameters[name].par[j]
         local qcName=effect.name.." "..effectParameters[name].nam[j]
         assignQC(program,qc,effect,parameter,program,qcName)
         print("QC"..qc..": "..qcName)
         qc=qc+1
       end
     end
   end
 else
   for i,effect in ipairs(effects) do
     local name=effect.name
     if effectParameters[name] then
       local parameter=effectParameters[name].par[2]
       local qcName=effect.name.." "..effectParameters[name].nam[2]
       assignQC(program,qc,effect,parameter,program,qcName)
       print("QC"..qc..": "..qcName)
       qc=qc+1
       if qc>8 then break end
     end
   end
 end
else
 print("No Effects Found")
end

Modwheel Vibrato

Looks for two empty modulation matrix rows and assigns LFO 1 to pitch.
It does so each time the script is reloaded so you might end up with duplicate assignments.

zones=this.parent:findZones(true)

function findEmptyRow(zone)
 for j=1,31 do
   local modRow1=zone:getModulationMatrixRow(j)
   local modRow2=zone:getModulationMatrixRow(j+1)
   if modRow1:getSource1()==0 and modRow2:getSource1()==0 then return j end
 end
end

function assignModwheelVibrato()
  for i,zone in ipairs(zones) do
    local j=findEmptyRow(zone)
    if j then
      local modRow1=zone:getModulationMatrixRow(j)
      local modRow2=zone:getModulationMatrixRow(j+1)
      modRow1:setSource1(15)
      modRow1:setParameter("Destination.Destination",46)
      modRow1:setParameter("Destination.Depth",41)
      modRow2:setSource1(1)
      modRow2:setParameter("Source1.Polarity",1)
      modRow2:setSource2(15)
      modRow2:setParameter("Destination.Destination",1)
      modRow2:setParameter("Destination.Depth",3)
      print("Zone "..i.." Modulation Row "..j.." and "..j+1)
      print("Modwheel assigned to Vibrato")
    else
      print("No Empty Modulation Row Found")
    end
  end
end

if zones[1] then
  assignModwheelVibrato()
else
  print("No Zones Found")
end

Aftertouch Vibrato

Same as above but with aftertouch.

zones=this.parent:findZones(true)

function findEmptyRow(zone)
 for j=1,31 do
   local modRow1=zone:getModulationMatrixRow(j)
   local modRow2=zone:getModulationMatrixRow(j+1)
   if modRow1:getSource1()==0 and modRow2:getSource1()==0 then return j end
 end
end

function assignAftertouchVibrato()
  for i,zone in ipairs(zones) do
    local j=findEmptyRow(zone)
    if j then
      local modRow1=zone:getModulationMatrixRow(j)
      local modRow2=zone:getModulationMatrixRow(j+1)
      modRow1:setSource1(16)
      modRow1:setParameter("Destination.Destination",46)
      modRow1:setParameter("Destination.Depth",41)
      modRow2:setSource1(1)
      modRow2:setParameter("Source1.Polarity",1)
      modRow2:setSource2(16)
      modRow2:setParameter("Destination.Destination",1)
      modRow2:setParameter("Destination.Depth",3)
      print("Zone "..i.." Modulation Row "..j.." and "..j+1)
      print("Aftertouch assigned to Vibrato")
    else
      print("No Emty Modulation Row Found")
    end
  end
end

if zones[1] then
  assignAftertouchVibrato()
else
  print("No Zones Found")
end

Reset Modulation Matrix

zones=this.parent:findZones(true)

function resetModulationMatrix()
  if zones[1] then
    for i,zone in ipairs(zones) do
      for j=1,32,1 do
        local modRow=zone:getModulationMatrixRow(j)
        local defs=modRow.parameterDefinitions
        for k,paremeter in ipairs(defs) do
          modRow:setParameter(paremeter.id,paremeter.default)
        end
      end
    end
    print("Modulation Matrix Has Been Reset")
  else
    print("No Zones Found")
  end
end

resetModulationMatrix()

Single Pitch knob

  1. Insert a new Midi-Module - Object / Lua-Script
  2. Edit the Lua-Script and write your code: (i named my synth-zone “Zone1a”)
--Amp and Filter Offsets
zone1=this.parent:getZone("Zone1a")

-- the callback-Function (it is called when you turn the knob in the Macro-Window)
function onO1ChangePitch()

O1_oct, Oct1rest = math.modf(Osc1Coarse / 12) 
Osc1PitFin=Osc1Coarse-(O1_oct*12)

O1_coarse, O1_fine=math.modf(Osc1PitFin)


-- this are the parameter in the layer (only OSC 1 & 2 activated)
zone1:setParameter("Osc 1.Octave", O1_oct)
zone1:setParameter("Osc 2.Octave", O1_oct)


zone1:setParameter("Osc 1.Coarse", O1_coarse) 
zone1:setParameter("Osc 2.Coarse", O1_coarse)


zone1:setParameter("Osc 1.Fine", O1_fine*100) 
zone1:setParameter("Osc 2.Fine", O1_fine*100)

end

-- This it the function/variable that can be linked with the Macro-Designer Extended when you select the luascript and look under PARAMSLIST

defineParameter("Osc1Pitch","Osc1Pitch",0,-36,36,onO1ChangePitch)
  1. copy this little programm (please excuse my bad “hacking”) in in the Lua-Script

  2. Create a Big Knob on the MaroDesigner-Page

  3. Open up “Maco Page Designer Extended”, select the Lua-Script and right click on the new variable Osc1Pitch.

  4. Select Connect to Macro Page

  5. rightclick on the Knob and select Connect to Parameter “Lua.Script…Osc1Pitch”

Great Idea!
I thought it might be even better to have this in a code management system and created a Git-Hub repository.
If you guys don’t mind it would be cool if you check in there. It is my first Git-Hub project, so please be patient with me :wink: Also I have nothing to upload yet because my code is not ready for prime time. But I thought I can help the community by creating that repository and take care of it:

https://github.com/tekkzoo/halion-script-collection

What do you think?

findZones() search filter:

It’s mentioned in the dev resources that findZones(), findLayers() etc. can include a function for to filter results. They don’t really expand on the implementation and didn’t mention that it needs to be an anonymous function. I tried a named function but couldn’t get it to work, the parameter doesn’t get passed to the function during the call.

Also, you have to include the recursive boolean otherwise the function isn’t recognised.

Using the filter can save a lot of memory, because now you have a table with only the relevant zone objects instead of all zones objects.

A simple filter to find sample zones:

zones = this.parent:findZones(true, function(result) 
	if result:getParameter("ZoneType") == 1 then return true end
end)

If you do need an external reusable function, call it from within the anonymous function: (finds sample and grain zones)

function filterFunc(result)
	zoneType = result:getParameter("ZoneType")
	if zoneType == 1 or zoneType == 2 then return true end
end

zones = this.parent:findZones(true, function(zone)
		if filterFunc(zone) then return true end
	end)

zoneTypes = {[0] = "Synth", "Sample", "Grain"}

for i, zoneFilt in ipairs(zones) do
	local zoneType = zoneTypes[zoneFilt:getParameter("ZoneType")]
	print(zoneType, zoneFilt.name)
end

Hi,

My first contribution here is actually a ruby script that creates a little halion lua snippet.

First of all the problem it solves:
Problem 1: HALion
-I wanted to switch samples of a zone using a dropdown on my macropage. But I did not want to load layers or change presets or anything that could potentially contain the path information.

  • The workarround is using a table with filename/path pairs. Like this:
sampleSearchPath = "/Users/myUser/Documents/Steinberg/HALion/Recordings/"

builtInSamples = {
    { name = "Growlar-A#5.wav", path = sampleSearchPath.."Growlar-A#5.wav"},
    { name = "Growlar-A#5.wav", path = sampleSearchPath.."Growlar-A#5.wav"},
    { name = "Growlar-A5.wav", path = sampleSearchPath.."Growlar-A5.wav"},
    { name = "Growlar-F#5.wav", path = sampleSearchPath.."Growlar-F#5.wav"},
    { name = "Growlar-G#5.wav", path = sampleSearchPath.."Growlar-G#5.wav"},
    { name = "Growlar-G5.wav", path = sampleSearchPath.."Growlar-G5.wav"},
}
  • Creating such a table is time intensive, so created a dumb little script to do that for me.
    Problem 2: LUA
    Halion script is restricted to halion. I could not find a way to access the filesystem because io and os are not available.
    I used ruby because io and os are bitches to use for what I do and ruby is perfect for file and text stuff. Also i did not want to use popen because ls is not available on windows per default(unless you use linux subsystem to run lua).

The tool is available here:
https://github.com/tekkzoo/halion-script-collection/blob/master/HALionFileListCreator.rb

I only tested this on mac because ruby is preinstalled here. If you have ruby installed on windows it should work there as well, please let me know.

usage: 
 ruby HALionFileListCreator.rb "[path of directory with samples]" "[outputfilename.lua]" 
 example: 
 ruby HALionFileListCreator.rb "/user/Documents/Steinberg/HALion/Recordings/" "/user/myBuiltinSamples.lua"

Changing a parameter to non-automatable:

I’m busy with a script which has quite a large number of parameters. It occurred to me that some of the parameters would be better off not generating automation.

I really wasn’t looking forward to redoing all those parameters by named arguments. To be honest, creating a large number of named argument parameters can get tedious even if you do it from the get go, so I thought of doing this:

function defineNonAuto(...)
	local nonAutoDef = {}
	local tableLength = #{...}
	local argFour = ({...})[4]
	
	nonAutoDef.name 		= 	({...})[1]
	nonAutoDef.longName		= 	({...})[2]
	nonAutoDef.default 		= 	({...})[3]
	nonAutoDef.onChanged	= 	({...})[tableLength]
	nonAutoDef.automatable 	= 	false
	
	if type(argFour) == "table" then 
		nonAutoDef.strings = argFour
	
	elseif type(argFour) == "number" then
		nonAutoDef.min = argFour
		nonAutoDef.max = ({...})[5]
		
		if tableLength == 7 then
			nonAutoDef.increment = ({...})[6]
		end
	end
	
	defineParameter(nonAutoDef)
end

Now you can simply change:

defineParameter("myParam", nil, 0, 0, 100, 1, onChangeFunc)

to

defineNonAuto("myParam", nil, 0, 0, 100, 1, onChangeFunc)

Just paste it in your script and you’re good to go.

It works for all parameter types, takes account of the optional increment value and works fine with callbacks taking arguments declared inside anonymous functions.

Key Switch

Midi module preset with a simple macropage.
Key Switch Midi Module.zip (2.22 KB)

layers = this.parent:findLayers()

defineParameter("DefaultSwitch", nil, this.parent:getParameterDefinition("LowKey"), function() getLayerNames() end)

function getLayerNames()
  layerNames = {}
  keySwitches = {}
  keyColor = getKeySwitches()
  for i, layer in ipairs(layers) do
    layerNames[i] = layer.name
    keySwitches[DefaultSwitch + i - 1] = i
    keyColor[i] = {name = layer.name, keyMin = DefaultSwitch + i - 1}
  end
end

getLayerNames()

defineParameter("LayerSelect", nil, 1, layerNames)


function isKeyswitch(event)
  if keySwitches[event.note] then
    LayerSelect = keySwitches[event.note]
    return true
  else
    return false
  end
end


function onNote(event)
  if not isKeyswitch(event) then
    playNote(event.note, event.velocity, -1, layers[LayerSelect])
  end
end

Midi Monitor

Midi module preset.

Program needs to have at least one zone. Doesn’t pick up Program Change messages.
Midi Monitor.zip (2.03 KB)

numberOfLines = 10
emptyLine = 1

for i = 1, numberOfLines do
  defineParameter("Line"..i, nil, "")
end

defineParameter("Reset", nil, false, function() resetResults() end)

function resetResults()
  if Reset then
    for i = 1, numberOfLines do
      _G["Line"..i] = ""
    end
    emptyLine = 1
    Reset = false
  end
end


function printLine(info)
  if emptyLine <= numberOfLines then
    _G["Line"..emptyLine] = info
    emptyLine = emptyLine + 1
  else
    for i = 1, numberOfLines - 1 do
      _G["Line"..i] = _G["Line"..i + 1]
    end
    _G["Line"..numberOfLines] = info
  end
end

function onNote(e)
  postEvent(e)
  printLine("Note On:   "..e.note.."   Velocity:   "..e.velocity)
end

function onRelease(e)
  postEvent(e)
  printLine("Note Off:   "..e.note.."   Velocity:   "..e.velocity)
end

function onController(e)
  postEvent(e)
  if e.controller==129 then
    printLine("Pitchbend:   "..e.value)
  else
    printLine("Controller:   "..e.controller.."   Value:   "..e.value)
  end
end


function onAfterTouch(e)
  postEvent(e)
  printLine("Aftertouch:   "..e.value)
end

Strummer

Midi module preset.

Attempt to create an arpeggiator that simulates strumming.
Doesn’t come with any patterns or presets. You need to save a user preset first for the preset browser to work.
Strummer.zip (4.77 KB)

for i = 1, 16 do
  defineParameter("Step"..i, nil, 0, {[0]="Off", "Down", "Up", "Mute"})
  defineParameter("Velocity"..i, nil, 100, 0, 127, 1)
end

defineParameter("StrumSpeed", nil, 10, 1, 40, 1)
defineParameter("BeatDiv", nil, 1, {"1/8", "1/16"})
defineParameter("numberOfSteps", nil, 8, 1, 16, 1)
defineParameter{
  name = "currentStep",
  default = 0,
  min = 0,
  max = 16,
  persistent = false,
  automatable = false,
  readOnly = true
  }

function checkTimeSignature()
  num, denom = getTimeSignature()
  if num > 0 then
    numberOfSteps = num*2
  else
    numberOfSteps = 8
  end
end

defineSlotLocal("noteBuffer")
defineSlotLocal("arpRunning")
defineSlotLocal("arpeggioNotes")
defineSlotLocal("heldNotes")

noteBuffer = {}
heldNotes = {}
arpRunning = false

function sortNotes()
  arpeggioNotes = {}
  for note, velocity in pairs(noteBuffer) do
    arpeggioNotes[#arpeggioNotes+1] = note
  end
  table.sort(arpeggioNotes)
end

function waitForBeat()
  if not isPlaying() then
    return 1
  else
    checkTimeSignature()
    local position = getBeatTimeInBar()
    if position == 0 then
      return 1
    else
      local b, f = math.modf(position)
      waitBeat(1-f)
      local beatfactor = 2*BeatDiv
      local step = beatfactor*math.ceil(position) + 1
      if step > numberOfSteps then
        step = 1
      end      
      return step
    end
  end
end

function waitForNextBeat()
  local position = getBeatTimeInBar()
  local b, f = math.modf(position)
  waitBeat(1-f)
end

function strumDown(velocity)
  local i = 1
  while arpeggioNotes[i] do
    if heldNotes[i] then
      releaseVoice(heldNotes[i])
    end
    local id = playNote(arpeggioNotes[i], velocity, 0)
    heldNotes[i] = id
    wait(StrumSpeed)
    i = i + 1
  end
end

function strumUp(velocity)
  local i = #arpeggioNotes
  while arpeggioNotes[i] do
    if heldNotes[i] then
      releaseVoice(heldNotes[i])
    end
    local id = playNote(arpeggioNotes[i], velocity, 0)
    heldNotes[i] = id
    wait(StrumSpeed)
    i = i - 1
  end
end

function strumMute()
  local i = 1
  while arpeggioNotes[i] do
    if heldNotes[i] then
      releaseVoice(heldNotes[i])
    end
    heldNotes[i] = nil
    i = i + 1
  end
end

function playStrum()
  arpRunning = true
  currentStep = waitForBeat()
  while arpeggioNotes[1] do
    local beatfactor = 2*BeatDiv
    local beat = getBeatDuration()/beatfactor
    local step = _G["Step"..currentStep]
    local velocity = _G["Velocity"..currentStep]
    if step == 1 then strumDown(velocity) end
    if step == 2 then strumUp(velocity) end
    if step == 3 then strumMute() end

    if isPlaying() and currentStep%beatfactor == 0 then
      checkTimeSignature()
      waitForNextBeat()
    else
      if step == 0 or step == 3 then
        wait(beat)
      else
        wait(beat-StrumSpeed*#arpeggioNotes)
      end
    end

    currentStep = currentStep + 1
    if currentStep > numberOfSteps then
      currentStep = 1
    end
  end
  arpRunning = false
  currentStep = 0
end

function onNote(event)
  noteBuffer[event.note] = event.velocity
  sortNotes()
  if arpRunning == false then
    playStrum()
  end
end

function onRelease(event)
  noteBuffer[event.note] = nil
  sortNotes()
  postEvent(event)
end

Hi,

I am really interested in these example scripts and I have managed to do some simple Lua scripts, but:

I really can not understand how you can import/load these user defined midi modules (.halmod). I tried to read the manuals and adjust options, but no avail. The user midi modules are in certain folder (and I’ve tried to show the path to Halion), but currently my Halion program sees only the factory midi modules (flex, random etc.)

On Windows the path should be: YourDocumentsFolder\Steinberg\HALion\Sub Presets\MIDI Modules

Halion will create this folder structure when you save a user preset (right click a Lua Script module and choose Midi Module Library/Save Module). If you haven’t saved a user preset yet you might need to create that folder structure manually. Or save a dummy preset and then put all the .halmod files in that folder.

Don’t know the path on Mac but you can use the dummy preset method to find the location of presets folder.

Once the presets are in correct folder you should be able to load them same way as all other factory midi modules.

I had no idea this was possible! I wish the manual documented this better. For anyone else just learning this for the first time. The macro page section of the manual mentions creating GUIs for your scripts to save them as proper midi modules. I have no idea why this information doesn’t exist in the midi module section or why it isn’t at least a note in it directing you to the macro page section. So if I create a script and macro page do I need to save both separately or does saving the script as a module encompass both functions? The interesting thing about this is that I read the part in the mono step modulator that mentioned it was scripted but I thought it was a typo because there seems to be no way to actually access the script. Now I know for sure we can actually script our own midi modules. The next thing would be figuring out how to get them to add to the modulation matrix.
Sketch2.png

Simple LFO Module

LFO.zip (3.25 KB)

defineParameter("Waveform", nil, 1, {"Sine", "Triangle", "Saw", "Square", "Ramp", "Log"})
defineParameter("Frequency", nil, 1, 0.1, 30)
defineParameter("Phase", nil, 0, 0, 360, 1)
defineParameter("SyncRate", nil, 3, {"1/1", "1/2", "1/4", "1/8", "1/16"})
defineParameter("Triplet", nil, false)
defineParameter("SyncMode", nil, 1, {"Off", "Tempo + Retrigger", "Tempo + Beat"}, function() syncModeChanged() end)
defineParameter("TempoSync", nil, false) --stack variable
defineParameter("Retrigger", nil, 1, {"Off", "First Note", "Each Note"})
defineParameter{name = "PhaseMonitor", default = 0, min = 0, max = 100, readOnly = true}

beatValues = {4,2,1,0.5,0.25}
phase = Phase
freq = Frequency
pi = math.pi

waveforms = {}
waveforms[1] = function() return math.sin(2 * pi * phase) end --sine
waveforms[2] = function() return 1 - math.abs((4 * (phase + 0.25)) % 4 - 2) end --triangle
waveforms[3] = function() return 1 - 2 * phase end --saw
waveforms[4] = function() return 2 * (2 * math.floor(phase) - math.floor(2 * phase)) + 1 end --square
waveforms[5] = function() return 2 * phase - 1 end --ramp
waveforms[6] = function() return 2 * math.pow(phase, 2) - 1 end --log

function syncModeChanged()
  if SyncMode == 1 then
    TempoSync = false
  else
    TempoSync = true
  end
end

defineSlotLocal("anyNotePressed")
anyNotePressed = 0

defineModulation("LFO", true)

function calcModulation()
  rate = getSamplingRate() / 32
  freq = Frequency
  if TempoSync then
    if Triplet then
      freq = getTempo() / 60 / (beatValues[SyncRate] * 2 / 3)
    else
      freq = getTempo() / 60 / beatValues[SyncRate]
    end
  end

  phase = phase + freq / rate
  if phase >= 1 then
    phase = phase - 1
  end

  if SyncMode == 3 and isPlaying() then
    local noteValue = beatValues[SyncRate]
    if Triplet then noteValue = noteValue * 2 / 3 end
    local position = getBeatTime()
    phase = (position % noteValue / noteValue) + Phase / 360
    if phase >= 1 then 
      phase = phase - 1
    end
  end

  PhaseMonitor = phase * 100

  return waveforms[Waveform]()
end

function onNote(event)
  anyNotePressed = anyNotePressed + 1
  if Retrigger ~= 1 and not (SyncMode == 3 and isPlaying()) then
    if Retrigger == 2 and anyNotePressed == 1 then phase = Phase / 360 end
    if Retrigger == 3 then phase = Phase / 360 end
  end
  postEvent(event)
end

function onRelease(event)
  anyNotePressed = math.max(anyNotePressed - 1, 0)
  postEvent(event)
end

Geez! You’re insanely good at this. When created manually like this is possible to be added to the modulation matrix?

Yes, it should appear in modulation matrix as source under Modulation Modules.

Lua module that can be loaded using “require” function.

Can be used to print display strings and values of integer parameters, generate hex ids for modulation matrix parameters or print information about parameters.
par.zip (1.27 KB)

local elements = {"Program", "Layer", "Zone", "ModulationMatrixRow", "MidiModule", "Bus", "Effect"}

local function checkElement(element)
  for i = 1, #elements do
    if type(element) == elements[i] then
      return true
    end
  end
  print("Bad argument #1\nElement object expected as first argument\n")
  return false
end

local function getElementName(element)
  local name = element.name
  if type(element) == "ModulationMatrixRow" then
    local zone = element.zone.name
    name = string.format("%s: %s", zone, element.name)
  end
  return name
end

local function camelcase(s)
  return string.gsub((string.gsub(s, "^%a", string.lower)), "%.", "").."s"
end

--boolean parameters bug
local function b(def, value)
  if def.type == "boolean" then
    if value == 1 then
      return true
    elseif value == 0 then
      return false
    else
      return value
    end
  else
    return value
  end
end

local function info(element, par)
  if not checkElement(element) then
    return
  else
    if not element:hasParameter(par) then
      print(string.format("Bad argument #2: %s doesn't have parameter: %q\n", element.type, par))
      return
    else
      local def = element:getParameterDefinition(par)

      print(string.format("%s:  \t\t%s\n", getElementName(element), def.name))

      print("Name:   \t\t"..def.name)
      print("Long Name:\t"..def.longName)
      print("IdDec:  \t\t"..def.id)
      print(string.format("IdHex:  \t\t%x", def.id))
      print("Type:   \t\t"..def.type)
      if def.unit ~= "" then
        print("Unit:   \t\t"..def.unit)
      end

      local value = element:getParameter(par)   

      if def.type == "integer" or def.type =="float" or def.type == "boolean" then
        --values
        print(string.format("Value:  \t\t %s", value))
        print(string.format("Default:\t\t %s", b(def, def.default)))
        print(string.format("Min:    \t\t %s", b(def, def.min)))
        print(string.format("Max:    \t\t %s\n", b(def, def.max)))
        --display values
        print("Display values:\n")
        print(string.format("Value:  \t\t %s", def:getDisplayString(value)))
        print(string.format("Default:\t\t %s", def:getDisplayString(b(def, def.default))))
        print(string.format("Min:    \t\t %s", def:getDisplayString(b(def, def.min))))
        print(string.format("Max:    \t\t %s", def:getDisplayString(b(def, def.max))))
      elseif def.type == "string" then
        print(string.format("Value:  \t\t %s", value))
      else
        print("Value:  \t\tNo display")
      end
      print("\n**********\n")
    end
  end
end

local function displayStrings(element, par)
  if not checkElement(element) then
    return
  else
    if not element:hasParameter(par) then
      print(string.format("Bad argument #2: %s doesn't have parameter: %q\n", element.type, par))
      return
    else
      local def = element:getParameterDefinition(par)
      if def.type == "integer" then
        print(string.format("%s = {", camelcase(def.name)))
        for i = def.min, def.max do
          local dis = def:getDisplayString(i)
          print(string.format("\t{name = %q,   \t   \t   \tindex = %s},", dis, i))
        end
        print("\t}")
      else
        print(string.format("Type of parameter %q is: %s", def.name, def.type))
      end
      print("\n**********\n")
    end
  end
end

local function mmHexList(par, short_opt)
  local zone = this.parent:findZones(true)[1]
  if not zone then
    print("No Zone found")
    return
  else
    local modRow = zone:getModulationMatrixRow(1)
    if not modRow:hasParameter(par) then
      print("Bad argument #1.\nModulation matrix row parameter expected.\n")
      return
    else
      local def = modRow:getParameterDefinition(par)
      local id = def.id
      print("Modulation matrix parameter: "..def.name.."    Rows 1 to 32:\n")
      for i = 1, 32 do
        if short_opt == true then
          print(string.format("@type:Zone/@id:%x%x", 252 + 4 * i, id))
        else
          print(string.format("@type:Zone/@matrix/@row:%s/@id:%x", i -1, id))
        end
      end
      print("\n**********\n")
    end
  end
end



return {
  info = info,
  displayStrings = displayStrings,
  mmHexList = mmHexList,
}

How to use:

  • Download and extract the attachment or create the file yourself by copying the script and saving it as par.lua
    You can name it whatever you want but then use the filename without extension with the “require” function.


  • In Halion go to Options/Scripting/Library Search Paths and add the folder containing the script file to Library Search Paths.

Now you should be able to load the module from any Lua Script module in Halion.

Example:

Create a program with at least one zone and script module. Paste the code:

local parameter = require 'par'
zone = this.parent:findZones(true)[1]

parameter.info(zone, "Amp.Level")
parameter.displayStrings(zone, "ZoneType")
parameter.mmHexList("Destination.Depth", true)