Last active
November 26, 2022 03:35
-
-
Save dhilowitz/38587ac7a9b5b73ed670ba1af7f251cb to your computer and use it in GitHub Desktop.
Decent Sampler to Kontakt script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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