User Script Examples

Post topics about HALion scripting.
Post Reply
misohoza
Member
Posts: 857
Joined: Sun Oct 05, 2014 12:18 am
Contact:

User Script Examples

Post by misohoza » Wed Feb 15, 2017 12:46 pm

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.
Win 10 Home, 64 bit, 8 gb ram,
Cubase Pro 9, Wavelab Pro 9, Halion 6, Dorico,
NI Komplete 10 Ultimate, Ozone 7,
UR 44

User avatar
abject39
Member
Posts: 317
Joined: Sat Jan 17, 2015 8:20 pm
Location: Ventura, Ca
Contact:

Re: User Script Examples

Post by abject39 » Tue Aug 01, 2017 4:18 am

misohoza wrote:
Wed Feb 15, 2017 12:46 pm
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.
Not a bad idea at all.
My vision is uncompromising: to transcend my clients dreams by mesmerizing their audience with the world's finest audio arrangements and products.

maggie
Member
Posts: 338
Joined: Fri Nov 01, 2013 1:08 pm
Contact:

Re: User Script Examples

Post by maggie » Tue Aug 01, 2017 10:22 am

+1
Cubase 10 pro, Halion 6, Groove Agent 5, Padshop Pro, Tone2 Electra 2, Tone2 Ultraspace, U-He Diva, U-He Presswerk, U-He Hive, Tubeohm Vintage, Virtual CZ, Arturia Solina V2, Arturia Analog Lab, Waldorf Nave, Synapse Dune 2, UVI Falcon, UVI Grand Piano Type D, AiR Xpand!2, KV331 Synthmaster, Steinberg UR22, Arturia Keylab 49 (Black Edition), Intel i7 2600, 16GB RAM, ASUS P8Z68-V/GEN 3, Windows 10 64bit

misohoza
Member
Posts: 857
Joined: Sun Oct 05, 2014 12:18 am
Contact:

Re: User Script Examples

Post by misohoza » Tue Aug 01, 2017 6:13 pm

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

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.03 KiB) Downloaded 222 times

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.

Code: Select all

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.

Code: Select all

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.

Code: Select all

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

Code: Select all

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()
Win 10 Home, 64 bit, 8 gb ram,
Cubase Pro 9, Wavelab Pro 9, Halion 6, Dorico,
NI Komplete 10 Ultimate, Ozone 7,
UR 44

User avatar
abject39
Member
Posts: 317
Joined: Sat Jan 17, 2015 8:20 pm
Location: Ventura, Ca
Contact:

Re: User Script Examples

Post by abject39 » Sun Aug 06, 2017 1:48 am

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")

Code: Select all

--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)

2) copy this little programm (please excuse my bad "hacking") in in the Lua-Script

3) Create a Big Knob on the MaroDesigner-Page

4) Open up "Maco Page Designer Extended", select the Lua-Script and right click on the new variable Osc1Pitch.
5) Select Connect to Macro Page
6) rightclick on the Knob and select Connect to Parameter "Lua.Script...Osc1Pitch"
Last edited by abject39 on Mon Aug 21, 2017 6:17 am, edited 1 time in total.
My vision is uncompromising: to transcend my clients dreams by mesmerizing their audience with the world's finest audio arrangements and products.

User avatar
Tekknovator
New Member
Posts: 23
Joined: Mon Aug 07, 2017 6:18 pm
Contact:

Re: User Script Examples

Post by Tekknovator » Fri Aug 11, 2017 5:19 pm

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 ;) 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?

AposMus
Junior Member
Posts: 184
Joined: Fri Nov 14, 2014 11:41 am
Contact:

Re: User Script Examples

Post by AposMus » Tue Aug 22, 2017 8:59 pm

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:

Code: Select all

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)

Code: Select all

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
Last edited by AposMus on Mon Oct 02, 2017 7:36 am, edited 1 time in total.
Cubase Pro 8.5.2
Halion 6
HSO
Padshop Pro
Dark Planet
Windows 7 64bit, AMD Phenom IIx6 1055t, 8GB Ram, Gigabyte GA-880G-UD3H F2, Nvidia GeForce GTS450
https://dewetvanderspuy.co.za/

User avatar
Tekknovator
New Member
Posts: 23
Joined: Mon Aug 07, 2017 6:18 pm
Contact:

Re: User Script Examples

Post by Tekknovator » Sun Sep 03, 2017 12:34 am

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:

Code: Select all

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 *witch* 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-scrip ... Creator.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.

Code: Select all

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

AposMus
Junior Member
Posts: 184
Joined: Fri Nov 14, 2014 11:41 am
Contact:

Re: User Script Examples

Post by AposMus » Mon Oct 02, 2017 7:35 am

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:

Code: Select all

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:

Code: Select all

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

Code: Select all

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.
Cubase Pro 8.5.2
Halion 6
HSO
Padshop Pro
Dark Planet
Windows 7 64bit, AMD Phenom IIx6 1055t, 8GB Ram, Gigabyte GA-880G-UD3H F2, Nvidia GeForce GTS450
https://dewetvanderspuy.co.za/

misohoza
Member
Posts: 857
Joined: Sun Oct 05, 2014 12:18 am
Contact:

Re: User Script Examples

Post by misohoza » Tue Dec 12, 2017 12:09 am

Key Switch

Midi module preset with a simple macropage.
Key Switch Midi Module.zip
(2.22 KiB) Downloaded 143 times

Code: Select all

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

Image
Win 10 Home, 64 bit, 8 gb ram,
Cubase Pro 9, Wavelab Pro 9, Halion 6, Dorico,
NI Komplete 10 Ultimate, Ozone 7,
UR 44

misohoza
Member
Posts: 857
Joined: Sun Oct 05, 2014 12:18 am
Contact:

Re: User Script Examples

Post by misohoza » Mon Mar 19, 2018 12:57 am

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 KiB) Downloaded 102 times

Code: Select all

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
Image
Win 10 Home, 64 bit, 8 gb ram,
Cubase Pro 9, Wavelab Pro 9, Halion 6, Dorico,
NI Komplete 10 Ultimate, Ozone 7,
UR 44

misohoza
Member
Posts: 857
Joined: Sun Oct 05, 2014 12:18 am
Contact:

Re: User Script Examples

Post by misohoza » Wed May 09, 2018 11:55 pm

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 KiB) Downloaded 88 times

Code: Select all

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
Image
Win 10 Home, 64 bit, 8 gb ram,
Cubase Pro 9, Wavelab Pro 9, Halion 6, Dorico,
NI Komplete 10 Ultimate, Ozone 7,
UR 44

Dart_em
New Member
Posts: 6
Joined: Sat Feb 06, 2016 2:23 pm
Contact:

Re: User Script Examples

Post by Dart_em » Sat Nov 03, 2018 7:21 pm

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.)

misohoza
Member
Posts: 857
Joined: Sun Oct 05, 2014 12:18 am
Contact:

Re: User Script Examples

Post by misohoza » Sat Nov 03, 2018 11:02 pm

Dart_em wrote:
Sat Nov 03, 2018 7:21 pm
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.
Win 10 Home, 64 bit, 8 gb ram,
Cubase Pro 9, Wavelab Pro 9, Halion 6, Dorico,
NI Komplete 10 Ultimate, Ozone 7,
UR 44

User avatar
abject39
Member
Posts: 317
Joined: Sat Jan 17, 2015 8:20 pm
Location: Ventura, Ca
Contact:

Re: User Script Examples

Post by abject39 » Sun Nov 11, 2018 11:06 pm

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
(128.4 KiB) Not downloaded yet
My vision is uncompromising: to transcend my clients dreams by mesmerizing their audience with the world's finest audio arrangements and products.

misohoza
Member
Posts: 857
Joined: Sun Oct 05, 2014 12:18 am
Contact:

Re: User Script Examples

Post by misohoza » Fri Nov 16, 2018 1:15 am

Simple LFO Module

LFO.zip
(3.25 KiB) Downloaded 62 times

Code: Select all

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

Image
Last edited by misohoza on Sat Nov 17, 2018 6:15 pm, edited 1 time in total.
Win 10 Home, 64 bit, 8 gb ram,
Cubase Pro 9, Wavelab Pro 9, Halion 6, Dorico,
NI Komplete 10 Ultimate, Ozone 7,
UR 44

User avatar
abject39
Member
Posts: 317
Joined: Sat Jan 17, 2015 8:20 pm
Location: Ventura, Ca
Contact:

Re: User Script Examples

Post by abject39 » Fri Nov 16, 2018 2:50 pm

Geez! You're insanely good at this. When created manually like this is possible to be added to the modulation matrix?
misohoza wrote:
Fri Nov 16, 2018 1:15 am
Simple LFO Module


LFO.zip

Code: Select all

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

Image
My vision is uncompromising: to transcend my clients dreams by mesmerizing their audience with the world's finest audio arrangements and products.

misohoza
Member
Posts: 857
Joined: Sun Oct 05, 2014 12:18 am
Contact:

Re: User Script Examples

Post by misohoza » Sat Nov 17, 2018 1:42 pm

abject39 wrote:
Fri Nov 16, 2018 2:50 pm
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.
Win 10 Home, 64 bit, 8 gb ram,
Cubase Pro 9, Wavelab Pro 9, Halion 6, Dorico,
NI Komplete 10 Ultimate, Ozone 7,
UR 44

misohoza
Member
Posts: 857
Joined: Sun Oct 05, 2014 12:18 am
Contact:

Re: User Script Examples

Post by misohoza » Wed Jan 02, 2019 7:37 pm

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 KiB) Downloaded 38 times

Code: Select all

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:

Code: Select all

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

parameter.info(zone, "Amp.Level")
parameter.displayStrings(zone, "ZoneType")
parameter.mmHexList("Destination.Depth", true)
Win 10 Home, 64 bit, 8 gb ram,
Cubase Pro 9, Wavelab Pro 9, Halion 6, Dorico,
NI Komplete 10 Ultimate, Ozone 7,
UR 44

Post Reply

Return to “Halion Scripting”

Who is online

Users browsing this forum: No registered users and 0 guests