Import your own sample?

Is it possible on the macro page to set up in your GUI a sort of “drag and drop” section where you can import your own samples into your instrument? Something like is possible with scripting in Kontakt 6?

Thanks in advance for any help!

Yes, it is. You need to create a Drop element on your macro page.

https://developer.steinberg.help/display/HMP/Drop

A little scripting might be necessary though. While you can connect the Drop to the SampleOsc.Filename parameter directly it will change just that… the file name. But it will not set the other parameters like sample end, loop points… Those will still have the same values as the previous sample.

But you can set those to correct values using script. In that case I would create a script parameter for the sample path and connect the Drop to it. Then use the script to change the zone parameters (filename, sample end…)

https://developer.steinberg.help/display/HSD/AudioFile

Hello, thanks for the replay!

is there a script example? I have searched this forum but it looks like nobody posted about this.

Does it look like both links you posted are not working?
Thanks!

Yes, the webpage is down for some reason right now.

You could try something like this:

defineParameter("Filename", nil, "", function() onFilenameChanged() end)

function onFilenameChanged()
    local zone = this.parent:getZone()
    local sample = AudioFile.open(Filename)
    if sample.valid then
        zone:setParameter("SampleOsc.Filename", sample.fileName)
        zone:setParameter("SampleOsc.SampleStart", 0)
        zone:setParameter("SampleOsc.SampleEnd", sample.length)
        -- check loops
        local loop = sample.loops[1]
        if loop then
            zone:setParameter("SampleOsc.SustainLoopModeA", 1)
            zone:setParameter("SampleOsc.SustainLoopStartA", loop.loopStart)
            zone:setParameter("SampleOsc.SustainLoopEndA", loop.loopEnd)
        else
            zone:setParameter("SampleOsc.SustainLoopModeA", 0)
            zone:setParameter("SampleOsc.SustainLoopStartA", 0)
            zone:setParameter("SampleOsc.SustainLoopEndA", 0)
        end
    end
end

You may need to adjust the zone variable to make sure it finds the sample zone you want.
Drop Sample.vstpreset (8.8 KB)

Thank you it works really well!

is not possible to add a line to have Halion automatically detect the pitch and set the correct root key of the sample? Just checked on the developer’s website but it looks like every page is not working!

It is. You can check if sample metadata contains rootkey and use that or try the pitch detection.

However if the user drops a drum sample or something that doesn’t have a clear pitch the detected pitch can be way off. This could be quite annoying. So setting the rootkey automatically without giving the user option to decide may not be the best option.

I was trying to add a line to have the pitch detected when I drop a sample, is this something close?
The developer website is still downn!
zone:setParameter(‘SampleOsc.Rootkey’, sample.pitch)

I cannot test it right now but I would try:

zone:setParameter(‘SampleOsc.Rootkey’, sample.rootKey or 60)

I just tried but it seems not working for me root is always set up as C3, I tried also to remove the “or 60 " part” but is always set up as C3 I tried with very stable pitch sample like piano

This will work only if the sample metada contains the rootkey information. It doesn’t do any pitch detection.

Ok, my sample name is like this but is not working:
Key 23_F3.wav
also tried this but not working too:
Key 23_F 3.wav

and how is working the script to auto-detect the pitch?

thanks a lot for any help

You can try this one:

defineParameter("Filename", nil, "", function() onFilenameChanged() end)
defineParameter("PitchDetectionProgress", nil, 0, 0, 100)

function setRootkey(sample)
    local zone = this.parent:getZone()
    local pitch, voiced = sample:getPitch(0, -1)
    local rootkey, detune = math.modf(pitch)
    if detune >= 0.5 then
        rootkey = rootkey + 1
        detune = detune - 1
    end
    detune = math.floor((detune * 100) + 0.5)
    print(pitch, voiced, rootkey, detune)
    if voiced then
        zone:setParameter("SampleOsc.Rootkey", rootkey)
        zone:setParameter("SampleOsc.Tune", detune)
    end
end


function onFilenameChanged()
    local zone = this.parent:getZone()
    local sample = AudioFile.open(Filename)
    if sample.valid then
        zone:setParameter("SampleOsc.Filename", sample.fileName)
        zone:setParameter("SampleOsc.SampleStart", 0)
        zone:setParameter("SampleOsc.SampleEnd", sample.length)
        -- check loops
        local loop = sample.loops[1]
        if loop then
            zone:setParameter("SampleOsc.SustainLoopModeA", 1)
            zone:setParameter("SampleOsc.SustainLoopStartA", loop.loopStart)
            zone:setParameter("SampleOsc.SustainLoopEndA", loop.loopEnd)
        else
            zone:setParameter("SampleOsc.SustainLoopModeA", 0)
            zone:setParameter("SampleOsc.SustainLoopStartA", 0)
            zone:setParameter("SampleOsc.SustainLoopEndA", 0)
        end
        -- rootkey and detune
        zone:setParameter("SampleOsc.Rootkey", sample.rootKey or 60)
        zone:setParameter("SampleOsc.Tune", sample.detune or 0)
        if not sample.rootKey then
            sample:analyzePitch(setRootkey)
            while sample:getPitchAnalysisProgress() < 1 do
                PitchDetectionProgress = sample:getPitchAnalysisProgress() * 100
                wait(250)
            end
            PitchDetectionProgress = 0
        else
            print("Rootkey from sample file: ", sample.rootKey)
        end
    end
end

It should try to read metadata first as this is quicker. If the rootkey information isn’t found then it does pitch detection.
Drop Sample 2.vstpreset (8.8 KB)

Thanks but it still not reading the metadata present in the file I upload.

My file is written like this:

Guitar 14_A3.wav

Why is not detecting the correct root key?

Di I have to write it in a specific way?

Well, it doesn’t matter what the file name is. It tries to read the rootkey from sample metadata if there is any. It doesn’t search for the pattern in the file name.

If there is no information in the sample metadata then it should try to analyse the sample itself (the audio, not the sample name).

Does it do anything if you drop a piano sample? Or something that has a clear and stable pitch?

I was thinking It was possible to do with a script something like this you can do in the Zone Editor (pls see screenshot attached).
That could be very helpful if it can read the pitch from the name file, also because based on my tests it detects the pitch correctly only if the sample has an extremely stable pitch… otherwise It almost always put C3

Screen Shot 2022-06-03 at 17.15.17 PM

I see. You can try this one. Not guaranteed to work 100% though.

defineParameter("Filename", nil, "", function() onFilenameChanged() end)
defineParameter("PitchDetectionProgress", nil, 0, 0, 100)

function setRootkey(sample)
    local zone = this.parent:getZone()
    local pitch, voiced = sample:getPitch(0, -1)
    local rootkey, detune = math.modf(pitch)
    if detune >= 0.5 then
        rootkey = rootkey + 1
        detune = detune - 1
    end
    detune = math.floor((detune * 100) + 0.5)
    print(pitch, voiced, rootkey, detune)
    if voiced then
        zone:setParameter("SampleOsc.Rootkey", rootkey)
        zone:setParameter("SampleOsc.Tune", detune)
    end
end

function keyTextFromSampleName(st)
    local notes = {c = 0, d = 2, e = 4, f = 5, g = 7, a = 9, b = 11}
    local mn = {}
    st = string.match(st, "([^\\/]+)%.%a+$")
    print(st)
    for k, s, n in string.gmatch(st, "[_%-%s]?([A-Ga-g])(%#?)(%-?%d)[_%-%s%.]?") do
        if k and n then
            local midinn = notes[string.lower(k)] + (s == "#" and 1 or 0) + (n + 2) * 12
            table.insert(mn, midinn)
            print(k, s, n, midinn)
        end
    end
    return mn[#mn]
end

function onFilenameChanged()
    local zone = this.parent:getZone()
    local sample = AudioFile.open(Filename)
    if sample.valid then
        zone:setParameter("SampleOsc.Filename", sample.fileName)
        zone:setParameter("SampleOsc.SampleStart", 0)
        zone:setParameter("SampleOsc.SampleEnd", sample.length)
        -- check loops
        local loop = sample.loops[1]
        if loop then
            zone:setParameter("SampleOsc.SustainLoopModeA", 1)
            zone:setParameter("SampleOsc.SustainLoopStartA", loop.loopStart)
            zone:setParameter("SampleOsc.SustainLoopEndA", loop.loopEnd)
        else
            zone:setParameter("SampleOsc.SustainLoopModeA", 0)
            zone:setParameter("SampleOsc.SustainLoopStartA", 0)
            zone:setParameter("SampleOsc.SustainLoopEndA", 0)
        end
        -- rootkey and detune
        local rootKey = sample.rootKey or keyTextFromSampleName(sample.fileName)
        zone:setParameter("SampleOsc.Rootkey", rootKey or 60)
        zone:setParameter("SampleOsc.Tune", sample.detune or 0)
        if not rootKey then
            sample:analyzePitch(setRootkey)
            while sample:getPitchAnalysisProgress() < 1 do
                PitchDetectionProgress = sample:getPitchAnalysisProgress() * 100
                wait(250)
            end
            PitchDetectionProgress = 0        
        end
    end
end

Thanks this works great but It looks like is working only for NON-looped samples.
If I try to load a looped sample it looks like is doing nothing, no written message in the script output message window. All looped samples I tried to load are mapped to C3, any idea why?

Don’t know. Loops and rootkey shouldn’t conflict in any way.

Can you upload some of the samples that cause problems? Maybe somewhere dropbox or similar and then post a link.

I made several tests with other samples and it looks like the problem is not if the sample is looped or not.
I tried to remove from line 55 “sample.rootKey or” so that the line now is like this:
local rootKey = keyTextFromSampleName(sample.fileName)
and it seems to work properly now… It looks like is making confusion with metadata in the sample?

You are probably right. I changed the script to help you troubleshoot. It should print the rootkey from sample attributes and also from sample file name. I guess the samples that were causing problems had the rootkey set in sample attributes. At the moment the script checks in this order:

sample attributes >> sample file name >> default (C3)

But this can be changed to look at the sample file name first and ignore the sample attributes.
As you already did:

Here is the updated script (still checking attributes first - you may want to change that)
I also removed the pitch detection.

defineParameter("Filename", nil, "", function() onFilenameChanged() end)

function keyTextFromSampleName(st)
    local notes = {c = 0, d = 2, e = 4, f = 5, g = 7, a = 9, b = 11}
    local mn = {}
    st = string.match(st, "([^\\/]+)%.%a+$")
    --print(st)
    for k, s, n in string.gmatch(st, "[_%-%s]([A-Ga-g])(%#?)(%-?%d)") do
        if k and n then
            local midinn = notes[string.lower(k)] + (s == "#" and 1 or 0) + (n + 2) * 12
            table.insert(mn, midinn)
            --print(k, s, n, midinn)
        end
    end
    return mn[#mn]
end

function onFilenameChanged()
    local zone = this.parent:getZone()
    local sample = AudioFile.open(Filename)
    if sample.valid then
        zone:setParameter("SampleOsc.Filename", sample.fileName)
        zone:setParameter("SampleOsc.SampleStart", 0)
        zone:setParameter("SampleOsc.SampleEnd", sample.length)
        -- check loops
        local loop = sample.loops[1]
        if loop then
            zone:setParameter("SampleOsc.SustainLoopModeA", 1)
            zone:setParameter("SampleOsc.SustainLoopStartA", loop.loopStart)
            zone:setParameter("SampleOsc.SustainLoopEndA", loop.loopEnd)
        else
            zone:setParameter("SampleOsc.SustainLoopModeA", 0)
            zone:setParameter("SampleOsc.SustainLoopStartA", 0)
            zone:setParameter("SampleOsc.SustainLoopEndA", 0)
        end
        -- rootkey and detune
        print(string.match(sample.fileName, "([^\\/]+)%.%a+$"))
        print("Rootkey from sample attributes: ", sample.rootKey)
        print("Rootkey from sample file name: ", keyTextFromSampleName(sample.fileName))
        print() -- empty line
        local rootKey = sample.rootKey or keyTextFromSampleName(sample.fileName)
        zone:setParameter("SampleOsc.Rootkey", rootKey or 60)
        zone:setParameter("SampleOsc.Tune", sample.detune or 0)
    end
end