Skip to content

Instantly share code, notes, and snippets.

@dhilowitz
Last active November 26, 2022 03:35
Show Gist options
  • Save dhilowitz/38587ac7a9b5b73ed670ba1af7f251cb to your computer and use it in GitHub Desktop.
Save dhilowitz/38587ac7a9b5b73ed670ba1af7f251cb to your computer and use it in GitHub Desktop.
Decent Sampler to Kontakt script
-- Decent Sampler to Kontakt script by David Hilowitz
-- This is a quick-and-dirty conversion script that has not been tested very much.
-- Also, there are a bunch of things that are not really configurable from within Creator Tools (alas),
-- which means that even though you may be able to get your samples imported correctly within Kontakt,
-- you will still have a bunch of work to do within Kontakt to get everything set up correctly.
-- Specifically, envelope modulation and round robin stuff can't be translated.
-- Round Robins can be especially complicated: Since Decent Sampler doesn't require you to have your
-- different round robins in separate groups, you may find yourself needed to move samples
-- This LUA script is meant to be run within Kontakt Creator Tools. The basic steps for using this script are:
-- 1. Launch Kontakt 6+
-- 2. Launch Creator Tools
-- 3. Create a blank Kontakt instrument
-- 4. Make sure that Creator Tools can "see" the new blank Kontakt instrument
-- 5. Switch to the "Lua Script" tab and load up this script
-- 6. Specify the path to the dspreset file
-- 7. Hit run. If all goes well, this should populate the Kontakt instrument tree above with groups and samples.
-- 8. If everything looks good, hit the "Push" button in the top-right hand corner of Creator Tools. This will send all of your data over to Kontakt.
-- In order to use this script you must turn on the IO module within Creator Tools. To do this:
-- 1. Launch Creator Tools
-- 2. Open up the Preferences screen (on Mac this is found in the "Creator Tools" menu)
-- 3. At the bottom of the Preferences screen, there is a section called "Lua Script Filesystem Permissions". Make sure that "Enabled Write and Execute Permissions" is checked.
-- Specify the path to the dspreset file you which to import here
local dsPresetPath = "/path/to/your/preset/Bass Guitar.dspreset"
if not filesystem.exists(dsPresetPath) then
print("The file '" .. dsPresetPath .. "' does not seem to exist. Please check the "..
"path above.")
return
end
print ("The preset is located at " .. dsPresetPath)
-- Check for valid instrument
if not instrument then
print("The following error message informs you that the Creator Tools is not "..
"focused on a Kontakt instrument. To solve this, load an instrument in "..
"Kontakt and select it from the instrument dropdown menu on top.")
return
end
-- print ("The samples are located in " .. dsPresetPath)
local xmlparser = require("xmlparser")
function str(t)
local orderedIndex = {}
for i in pairs(t) do
table.insert(orderedIndex, i)
end
table.sort(orderedIndex)
local s, e = '{'
for k, i in pairs(orderedIndex) do
e = t[i]
if type(e) == 'table' then
e = str(e)
end
s = s .. i .. ':' .. e .. ','
end
return s .. '}'
end
function linearOrDbStringToLinear(inputString)
local dbIndex = string.find(inputString, "dB")
if dbIndex then
local decibels = tonumber(string.sub(inputString, 0, dbIndex-1))
if(decibels < -100) then
decibels = 100
end
if(decibels > 24) then
decibels = 24
end
if decibels > -100 then
return 10^(decibels * 0.05)
else
return 0
end
else
return tonumber(inputString)
end
end
local presetDoc, err = xmlparser.parseFile(dsPresetPath)
if err then
print('There was an issue parsing the XML')
if err then print(' ' .. err .. '/' .. filename) end
end
for i, topLevelTag in pairs(presetDoc.children) do
if topLevelTag.tag == "DecentSampler" then
for i, level2Tag in pairs(topLevelTag.children) do
if level2Tag.tag == "groups" then
-- Reset the instrument groups.
instrument.groups:reset()
local groupIndex = 0
local instrumentSamplePath = (level2Tag.attrs["path"] and level2Tag.attrs["path"] ~= '') and level2Tag.attrs["path"] or nil
local instrumentVolume = (level2Tag.attrs["volume"] and level2Tag.attrs["volume"] ~= '') and level2Tag.attrs["volume"] or 0
local instrumentPan = (level2Tag.attrs["pan"] and level2Tag.attrs["pan"] ~= '') and level2Tag.attrs["pan"] or 0
local instrumentTuning = (level2Tag.attrs["tuning"] and level2Tag.attrs["tuning"] ~= '') and level2Tag.attrs["tuning"] or 0
local instrumentRootNote = (level2Tag.attrs["rootNote"] and level2Tag.attrs["rootNote"] ~= '') and level2Tag.attrs["rootNote"] or 48
local instrumentLoNote = (level2Tag.attrs["loNote"] and level2Tag.attrs["loNote"] ~= '') and level2Tag.attrs["loNote"] or 0
local instrumentHiNote = (level2Tag.attrs["hiNote"] and level2Tag.attrs["hiNote"] ~= '') and level2Tag.attrs["hiNote"] or 127
local instrumentLoVel = (level2Tag.attrs["loVel"] and level2Tag.attrs["loVel"] ~= '') and level2Tag.attrs["loVel"] or 0
local instrumentHiVel = (level2Tag.attrs["hiVel"] and level2Tag.attrs["hiVel"] ~= '') and level2Tag.attrs["hiVel"] or 127
local instrumentStart = (level2Tag.attrs["start"] and level2Tag.attrs["start"] ~= '') and level2Tag.attrs["start"] or 0
local instrumentSampleEnd = (level2Tag.attrs["end"] and level2Tag.attrs["end"] ~= '') and level2Tag.attrs["end"] or nil
local instrumentLoopStart = level2Tag.attrs["loopStart"]
local instrumentLoopEnd = level2Tag.attrs["loopEnd"]
local instrumentLoopCrossfade = level2Tag.attrs["loopCrossfade"]
for i, group in pairs(level2Tag.children) do
if group.tag == "group" then
local groupSamplePath = group.attrs["path"]
local groupVolume = group.attrs["volume"]
local groupPan = group.attrs["pan"]
local groupTuning = group.attrs["tuning"]
local groupRootNote = group.attrs["rootNote"]
local groupLoNote = group.attrs["loNote"]
local groupHiNote = group.attrs["hiNote"]
local groupLoVel = group.attrs["loVel"]
local groupHiVel = group.attrs["hiVel"]
local groupStart = group.attrs["start"]
local groupSampleEnd = group.attrs["end"]
local groupLoopStart = group.attrs["loopStart"]
local groupLoopEnd = group.attrs["loopEnd"]
local groupLoopCrossfade = group.attrs["loopCrossfade"]
local g = Group()
if groupIndex >= #instrument.groups then
instrument.groups:add(g)
end
if group.attrs["name"] ~= nil then
instrument.groups[groupIndex].name = group.attrs["name"]
end
if group.attrs["groupVolume"] ~= nil then
instrument.groups[groupIndex].volume = group.attrs["groupVolume"]
end
instrument.groups[groupIndex].zones:reset()
for i, sample in pairs(group.children) do
if sample.tag == "sample" then
print("sample " .. sample.tag)
local z = Zone()
-- Add a zone for each sample.
instrument.groups[groupIndex].zones:add(z)
-- local a = (b and b ~= '') and b or (c and c ~= '') and c or d
local samplePath = sample.attrs["path"] or groupSamplePath or instrumentSamplePath
local volume = sample.attrs["volume"] or groupVolume or instrumentVolume
local pan = sample.attrs["pan"] or groupPan or instrumentPan
local tuning = sample.attrs["tuning"] or groupTuning or instrumentTuning
local rootNote = sample.attrs["rootNote"] or groupRootNote or instrumentRootNote
local loNote = sample.attrs["loNote"] or groupLoNote or instrumentLoNote
local hiNote = sample.attrs["hiNote"] or groupHiNote or instrumentHiNote
local loVel = sample.attrs["loVel"] or groupLoVel or instrumentLoVel
local hiVel = sample.attrs["hiVel"] or groupHiVel or instrumentHiVel
local start = sample.attrs["start"] or groupStart or instrumentStart
local sampleEnd = sample.attrs["end"] or groupSampleEnd or instrumentSampleEnd
local loopStart = sample.attrs["loopStart"] or groupLoopStart or instrumentLoopStart
local loopEnd = sample.attrs["loopEnd"] or groupLoopEnd or instrumentLoopEnd
local loopCrossfade = sample.attrs["loopCrossfade"] or groupLoopCrossfade or instrumentLoopCrossfade
if not samplePath then
print("No sample path defined!")
return
end
z.file = filesystem.parentPath(dsPresetPath) .. "/" .. samplePath
z.volume = linearOrDbStringToLinear(volume)
z.pan = pan
z.tune = tuning
z.rootKey = sample.attrs['rootNote']
z.keyRange.low = loNote
z.keyRange.high = hiNote
z.velocityRange.low = loVel
z.velocityRange.high = hiVel
z.sampleStart = start
if sampleEnd ~= nil then
z.sampleEnd = sampleEnd
end
if loopStart or loopEnd or loopCrossfade then
z.loops[0].mode = 1
z.loops[0].start = loopStart
z.loops[0].length = loopEnd - loopStart
z.loops[0].xfade = loopCrossfade
end
print("sample rootNote" .. z.rootKey)
end
end
groupIndex = groupIndex + 1
end
end
end
end
end
end
-- from https://github.com/jonathanpoelen/xmlparser
local io, string, pairs = io, string, pairs
-- http://lua-users.org/wiki/StringTrim
local trim = function(s)
local from = s:match"^%s*()"
return from > #s and "" or s:match(".*%S", from)
end
local slashchar = string.byte('/', 1)
local E = string.byte('E', 1)
function parse(s, evalEntities)
-- remove comments
s = s:gsub('<!%-%-(.-)%-%->', '')
local entities, tentities = {}
if evalEntities then
local pos = s:find('<[_%w]')
if pos then
s:sub(1, pos):gsub('<!ENTITY%s+([_%w]+)%s+(.)(.-)%2', function(name, q, entity)
entities[#entities+1] = {name=name, value=entity}
end)
tentities = createEntityTable(entities)
s = replaceEntities(s:sub(pos), tentities)
end
end
local t, l = {}, {}
local addtext = function(txt)
txt = txt:match'^%s*(.*%S)' or ''
if #txt ~= 0 then
t[#t+1] = {text=txt}
end
end
s:gsub('<([?!/]?)([-:_%w]+)%s*(/?>?)([^<]*)', function(type, name, closed, txt)
-- open
if #type == 0 then
local attrs, orderedattrs = {}, {}
if #closed == 0 then
local len = 0
for all,aname,_,value,starttxt in string.gmatch(txt, "(.-([-_%w]+)%s*=%s*(.)(.-)%3%s*(/?>?))") do
len = len + #all
attrs[aname] = value
orderedattrs[#orderedattrs+1] = {name=aname, value=value}
if #starttxt ~= 0 then
txt = txt:sub(len+1)
closed = starttxt
break
end
end
end
t[#t+1] = {tag=name, attrs=attrs, children={}, orderedattrs=orderedattrs}
if closed:byte(1) ~= slashchar then
l[#l+1] = t
t = t[#t].children
end
addtext(txt)
-- close
elseif '/' == type then
t = l[#l]
l[#l] = nil
addtext(txt)
-- ENTITY
elseif '!' == type then
if E == name:byte(1) then
txt:gsub('([_%w]+)%s+(.)(.-)%2', function(name, q, entity)
entities[#entities+1] = {name=name, value=entity}
end, 1)
end
-- elseif '?' == type then
-- print('? ' .. name .. ' // ' .. attrs .. '$$')
-- elseif '-' == type then
-- print('comment ' .. name .. ' // ' .. attrs .. '$$')
-- else
-- print('o ' .. #p .. ' // ' .. name .. ' // ' .. attrs .. '$$')
end
end)
return {children=t, entities=entities, tentities=tentities}
end
function parseFile(filename, evalEntities)
local f, err = io.open(filename)
return f and parse(f:read'*a', evalEntities), err
end
function defaultEntityTable()
return { quot='"', apos='\'', lt='<', gt='>', amp='&', tab='\t', nbsp=' ', }
end
function replaceEntities(s, entities)
return s:gsub('&([^;]+);', entities)
end
function createEntityTable(docEntities, resultEntities)
entities = resultEntities or defaultEntityTable()
for _,e in pairs(docEntities) do
e.value = replaceEntities(e.value, entities)
entities[e.name] = e.value
end
return entities
end
return {
parse = parse,
parseFile = parseFile,
defaultEntityTable = defaultEntityTable,
replaceEntities = replaceEntities,
createEntityTable = createEntityTable,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment