Newbie question: handling ids of played notes?

In my script a note plays samples from several layers (several microphones). So I have something like this:

id1=playNote(event.note, event.velocity, -1, layers[1])
id7=playNote(event.note, event.velocity, -1, layers[7])
id22=playNote(event.note, event.velocity, -1, layers[22])

Do I understand correctly that there is no other way rather than dealing with all three ids later in the script? For examples to change volume or pan. Is there an elegant way to do it? Like putting in one multidimensional array or so?

I’m asking because, for example, in Kontakt allowing of several groups (layers) doesn’t multiply notes ids. The id is one and it’s pretty easy to deal with it later on. Schematically it goes like this:

id=play_note()
allow group(1) for id
allow group(7) for id
allow group(22) for id

And in Halion for just one pressed key I got a bunch of ids I must remember about.

You can put the note ids in a table and then loop through the table when you need to change the pan or volume.

Another way could be using the LayerMidiMute parameter. In this case you only need to deal with one note id.

I’ve tried this approach like this

function onNote(event)

--mute all layers--
for i = 1, #layers do
local layer = layers[i]
layer:setParameter("LayerMidiMute", true)
end

--allow a necessary layer--
local layer = layers[1]
    layer:setParameter("LayerMidiMute", false)
local layer = layers[7]
    layer:setParameter("LayerMidiMute", false)
local layer = layers[22]
    layer:setParameter("LayerMidiMute", false)

--play note and store its id in array--
id[event.note]=playNote(event.note,event. velocity, -1)

end 

It worked perfectly until I added the same block OnRelease

function OnRelease(event)

--mute all layers--
for i = 1, #layers do
local layer = layers[i]
layer:setParameter("LayerMidiMute", true)
end

--allow a release  layer--

local layer = layers[3]
layer:setParameter(“LayerMidiMute”, false)

--play note and store its id in array--
local id=playNote(event.note,event.velocity, -1)

end 

Now if I play with legato (overlapping notes) releasing the previous key chokes the sound of the next note. I know that choking comes from --mute all layers-- block in onRelease, but I don’t know why it affects already playing samples? And nothing like this happens in OnNote block when I press several keys one after another.

Try table.insert

table.insert(ids, id1)
table.insert(ids, id7)
...

for i, id in ipairs(ids) do
[something]
end

LayerMidiMute isn’t exactly the same as allow/disallow group in Kontakt. It literally mutes the layer (midi).

Is it necessary to do it from onNote and onRelease for each note separately? If you have parameters for switching microphones you can use their callbacks to mute/unmute layers you need.

You say it works until you bring in the release samples. You can create 2 main layers (note on, release samples). Inside those 2 main layers put as many sublayers as you need. Then you can deal with note on samples and release samples separately. Including muting and unmuting.

What @Maestro suggested is also a good idea. That is if you decide to use the other approach.

ids = {}
for i = 0, 127 do
    ids[i] = {}
end

function onNote(e)
    local id = playNote(e.note, e.velocity)
    table.insert(ids[e.note], id)
end

function onRelease(e)
    ids[e.note] = {}
end

Maybe you can make it even shorter.

ids = {}
for i = 0, 127 do
    ids[i] = {}
end

function onNote(e)
    table.insert(ids[e.note], playNote(e.note, e.velocity))
end

function onRelease(e)
    ids[e.note] = {}
end

So does LayerMidiMute stop layers from receiving mid-massages or it chokes the voices already playing? To my understanding it shouldn’t.

And yes, I need to play a separate layer OnRelease with key release noises.

Try this:
Open parameter list so you have access to LayerMidiMute parameter. Press and hold a note on the midi keyboard. Turn on the LayerMidiMute. It will choke the note even if you still hold the key.

That’s why it’s probably not a good idea to do this (mute all layers) with each note. The idea was that you mute and unmute layers as needed in advance, when you select microphone and then just post the events. Only unmuted layers should play. But I wouldn’t change the mute states from onNote and onRelease. At least not muting everything.

Ok, one more question. In the above example in the line

table.insert(ids[e.note], playNote(e.note, e.velocity))

you use ids[e.note]. But it’s a value from array ids, not ids array itself. Could you explain it?

The ids[e.note] is a table inside ids table.

Empty table is created for each note inside the ids table. So you can store multiple note ids for the same note.

Thinking about it, you could also do it like this. It may be easier if you need to loop through all note ids.

ids = {}

function onNote(e) 
    if not ids[e.note] then
        ids[e.note] = {}
    end
    table.insert(ids[e.note], playNote(e.note, e.velocity)) 
end 

function onRelease(e)
     ids[e.note] = nil
end

Ok, I understood the idea of creating 128 dynamic tables. I provided filling up of such table related to a particular note as new layers (mics) are engaged for that note. However as I tried to change pan for all three mics I got en error (seems like related to addressing the table with ids). My script is below:

played_note_id={}
for i = 0, 127 do
    played_note_id[i] = {}
end

function onRelease(event)      
   
    if (mic1==1) then
      local id= playNote(event.note, event.velocity, -1, layers[1])
      table.insert(played_note_id[event.note], id)
    end
    
    if (mic2==1) then
      local id= playNote(event.note, event.velocity, -1, layers[2])
      table.insert(played_note_id[event.note], id)
    end
    
    if (mic3==1) then
      local id= playNote(event.note, event.velocity, -1, layers[3])
      table.insert(played_note_id[event.note], id)
    end


changePan(played_note_id[event.note],-1.0,false,true)
  
end

played_note_id[event.note] is a table with note ids.
changePan needs a note id not the table istelf.

for _, id in ipairs(played_note_id[event.note]) do
    changePan(id, -1, false, true)
end

However you probable also need a way to get rid of old ids. Otherwise the table with note ids will just grow.

Here is an example of changing pan, volume and tune of already played notes. There are probably more ways to do it. Depending on what you need. It doesn’t deal with release samples though.

ids = {}

defineParameter("Tune", nil, 0, -12, 12, function() runSync(changeTune2) end)
defineParameter("Pan", nil, 0, -100, 100, function() runSync(changePan2) end)
defineParameter("Volume", nil, 100, 0, 100, function() runSync(changeVolume2) end)

-- wrap changeTune, changePan, changeVolume
function changeTune2()
    for _, idsTable in pairs(ids) do
        for i, id in ipairs(idsTable) do
            changeTune(id, Tune)
        end
    end
end

function changePan2()
    for _, idsTable in pairs(ids) do
        for i, id in ipairs(idsTable) do
            changePan(id, Pan / 100)
        end
    end
end

function changeVolume2()
    for _, idsTable in pairs(ids) do
        for i, id in ipairs(idsTable) do
            changeVolume(id, Volume / 100)
        end
    end
end


function onNote(e)
    if not ids[e.note] then
        ids[e.note] = {}
    end
    table.insert(ids[e.note], playNote(e.note, e.velocity, -1, nil, Volume / 100, Pan / 100, Tune))
end

function onRelease(e)
    ids[e.note] = nil
end

Looking at your onRelease function, is that all you need to do? Change pan of release samples?

Maybe you don’t need that global ids table. You could create a local table inside onRelease and loop through that. That would solve the problem how to get rid of old ids.

function onRelease(event)
    local ids = {}

    if (mic1==1) then
        local id= playNote(event.note, event.velocity, -1, layers[1])
        table.insert(ids, id)
    end

    if (mic2==1) then
        local id= playNote(event.note, event.velocity, -1, layers[2])
        table.insert(ids, id)
    end

    if (mic3==1) then
        local id= playNote(event.note, event.velocity, -1, layers[3])
        table.insert(ids, id)
    end


    for i, id in ipairs(ids) do
        changePan(id, -1, false, true)
    end
end

Or alternatively just change the pan right after you play the note. In that case you don’t need any table at all.

function onRelease(event)

    if (mic1==1) then
        local id= playNote(event.note, event.velocity, -1, layers[1])
        changePan(id, -1, false, true)
    end

    if (mic2==1) then
        local id= playNote(event.note, event.velocity, -1, layers[2])
        changePan(id, -1, false, true)
    end

    if (mic3==1) then
        local id= playNote(event.note, event.velocity, -1, layers[3])
        changePan(id, -1, false, true)
    end

end

Or another way:

function onRelease(event)
    
    for i = 1, 3 do
        if _G["mic"..i] == 1 then
            local id = playNote(event.note, event.velocity, -1, layers[i])
            changePan(id, -1, false, true)
        end
    end    

end

playNote has additional optional arguments (volume, pan, tune). You could also use those.

function onRelease(event)

    for i = 1, 3 do
        if _G["mic"..i] == 1 then
            playNote(event.note, event.velocity, -1, layers[i], 1, -1)            
        end
    end    

end

Thanks for the efforts to help. Actually, I want to understand how to deal with a bunch of ids, which are fired up by the same note only in different layers. ChangePan is just an example, I might want later on to fade all these ids or compare them, etc. So in general I want to store and read all ids belonging to the same note in an elegant and easy way. And I think that a two-dimensional array is the best option, i.e. played_note_id[event.note][mic#]

Also could you please explain using a _G array? Why is it underscored in the beginning? And why isn’t it just if “mic”…i == 1 then? Thanks a lot!

It depends whether you need to store those ids globally (like my example above - change all played notes) or only need ids of one particular note inside onNote or onRelease.

I would use local table inside onNote and onRelease if that works for what you need. You can index it however you want. It’s just a table. Just be be aware you can only use ipairs if the table doesn’t have wholes.

_G is global environment. It’s a table where lua stores global variables. Parameters are treated the same way as global variables.