entry: this.parameters.entrySet ())
+ out.add (entry.getKey () + " = " + quote (entry.getValue ()));
+
+ // The mapping slot is written last (matches the device's file layout). One-Shot presets
+ // carry their single sample in the parameters block and have no mapping slot.
+ if (!this.keyZones.isEmpty () && this.machine != Machine.UNKNOWN && this.machine != Machine.ONESHOT)
+ {
+ final String prefix = "parameters." + this.machine.getParameterPrefix () + "_mapping_slot";
+ out.add ("");
+ out.add ("[" + prefix + "]");
+ out.add ("version = " + this.mappingSlotVersion);
+ out.add ("name = " + quote (this.mappingSlotName));
+
+ for (final TonverkKeyZone zone: this.keyZones)
+ {
+ out.add ("");
+ out.add ("[[" + prefix + ".key-zones]]");
+ out.add ("pitch = " + zone.pitch);
+ out.add ("key-center = " + formatNumber (zone.keyCenter));
+
+ for (final TonverkVelocityLayer layer: zone.velocityLayers)
+ {
+ out.add ("");
+ out.add ("[[" + prefix + ".key-zones.velocity-layers]]");
+ out.add ("velocity = " + formatNumber (layer.velocity));
+ if (layer.strategy != null)
+ out.add ("strategy = " + quote (layer.strategy));
+
+ for (final TonverkSampleSlot slot: layer.sampleSlots)
+ {
+ out.add ("");
+ out.add ("[[" + prefix + ".key-zones.velocity-layers.sample-slots]]");
+ out.add ("sample = " + quote (slot.sample));
+ if (slot.loopMode != null)
+ out.add ("loop-mode = " + quote (slot.loopMode));
+ if ("Forward".equals (slot.loopMode))
+ {
+ if (slot.loopStart != null && slot.loopStart.intValue () >= 0)
+ out.add ("loop-start = " + slot.loopStart);
+ if (slot.loopEnd != null && slot.loopEnd.intValue () >= 0)
+ out.add ("loop-end = " + slot.loopEnd);
+ if (slot.loopCrossfade != null && slot.loopCrossfade.intValue () >= 0)
+ out.add ("loop-crossfade = " + slot.loopCrossfade);
+ if (slot.keepLoopingOnRelease != null && slot.keepLoopingOnRelease.booleanValue ())
+ out.add ("keep-looping-on-release = true");
+ }
+ }
+ }
+ }
+ }
+
+ Files.write (path, out);
+ }
+
+
+ /**
+ * Get a parameter value as text.
+ *
+ * @param key The parameter name
+ * @return The value or null if not present
+ */
+ public String param (final String key)
+ {
+ return this.parameters.get (key);
+ }
+
+
+ /**
+ * Get a parameter value as a floating point number.
+ *
+ * @param key The parameter name
+ * @param defaultValue The value to return if the parameter is missing or not a number
+ * @return The value
+ */
+ public double paramDouble (final String key, final double defaultValue)
+ {
+ final String value = this.parameters.get (key);
+ if (value == null || value.isBlank ())
+ return defaultValue;
+ try
+ {
+ return Double.parseDouble (value);
+ }
+ catch (final NumberFormatException ex)
+ {
+ return defaultValue;
+ }
+ }
+
+
+ /**
+ * Get a parameter value as an integer number.
+ *
+ * @param key The parameter name
+ * @param defaultValue The value to return if the parameter is missing or not a number
+ * @return The value
+ */
+ public int paramInt (final String key, final int defaultValue)
+ {
+ final String value = this.parameters.get (key);
+ if (value == null || value.isBlank ())
+ return defaultValue;
+ try
+ {
+ return (int) Math.round (Double.parseDouble (value));
+ }
+ catch (final NumberFormatException ex)
+ {
+ return defaultValue;
+ }
+ }
+
+
+ private void assignKeyZone (final TonverkKeyZone keyZone, final String key, final String value)
+ {
+ switch (key)
+ {
+ case "pitch" -> keyZone.pitch = this.parseIntSafe (value, 0);
+ case "key-center" -> keyZone.keyCenter = this.parseDoubleSafe (value, 0);
+ default -> this.errors.add ("Unknown key-zone tag: " + key);
+ }
+ }
+
+
+ private void assignVelocityLayer (final TonverkVelocityLayer velocityLayer, final String key, final String value)
+ {
+ switch (key)
+ {
+ case "velocity" -> velocityLayer.velocity = this.parseDoubleSafe (value, 0);
+ case "strategy" -> velocityLayer.strategy = value;
+ default -> this.errors.add ("Unknown velocity-layer tag: " + key);
+ }
+ }
+
+
+ private void assignSampleSlot (final TonverkSampleSlot sampleSlot, final String key, final String value)
+ {
+ switch (key)
+ {
+ case "sample" -> sampleSlot.sample = value;
+ case "loop-mode" -> sampleSlot.loopMode = value;
+ case "loop-start" -> sampleSlot.loopStart = Integer.valueOf (this.parseIntSafe (value, 0));
+ case "loop-end" -> sampleSlot.loopEnd = Integer.valueOf (this.parseIntSafe (value, 0));
+ case "loop-crossfade" -> sampleSlot.loopCrossfade = Integer.valueOf (this.parseIntSafe (value, 0));
+ case "keep-looping-on-release" -> sampleSlot.keepLoopingOnRelease = Boolean.valueOf (value);
+ case "trim-start" -> sampleSlot.trimStart = Integer.valueOf (this.parseIntSafe (value, 0));
+ case "trim-end" -> sampleSlot.trimEnd = Integer.valueOf (this.parseIntSafe (value, 0));
+ default -> this.errors.add ("Unknown sample-slot tag: " + key);
+ }
+ }
+
+
+ private int parseIntSafe (final String value, final int defaultValue)
+ {
+ try
+ {
+ return (int) Math.round (Double.parseDouble (value.trim ()));
+ }
+ catch (final NumberFormatException ex)
+ {
+ this.errors.add ("Not an integer: " + value);
+ return defaultValue;
+ }
+ }
+
+
+ private double parseDoubleSafe (final String value, final double defaultValue)
+ {
+ try
+ {
+ return Double.parseDouble (value.trim ());
+ }
+ catch (final NumberFormatException ex)
+ {
+ this.errors.add ("Not a number: " + value);
+ return defaultValue;
+ }
+ }
+
+
+ private static String stripSectionBrackets (final String line)
+ {
+ String result = line.trim ();
+ while (result.startsWith ("["))
+ result = result.substring (1);
+ while (result.endsWith ("]"))
+ result = result.substring (0, result.length () - 1);
+ return result.trim ();
+ }
+
+
+ private static String stripQuotes (final String value)
+ {
+ final String trimmed = value.trim ();
+ if (trimmed.length () >= 2 && trimmed.startsWith ("'") && trimmed.endsWith ("'"))
+ return trimmed.substring (1, trimmed.length () - 1);
+ return trimmed;
+ }
+
+
+ private static String quote (final String value)
+ {
+ return "'" + (value == null ? "" : value) + "'";
+ }
+
+
+ private static String formatNumber (final double value)
+ {
+ if (value == Math.rint (value) && !Double.isInfinite (value))
+ return Long.toString ((long) value) + ".0";
+ return Double.toString (value);
+ }
+}
diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/elektron/TonverkValues.java b/src/main/java/de/mossgrabers/convertwithmoss/format/elektron/TonverkValues.java
new file mode 100644
index 00000000..d58a5c33
--- /dev/null
+++ b/src/main/java/de/mossgrabers/convertwithmoss/format/elektron/TonverkValues.java
@@ -0,0 +1,226 @@
+// Written by Jürgen Moßgraber - mossgrabers.de
+// (c) 2019-2026
+// Licensed under LGPLv3 - http://www.gnu.org/licenses/lgpl-3.0.txt
+
+package de.mossgrabers.convertwithmoss.format.elektron;
+
+/**
+ * Conversions between the normalized parameter values stored in a Tonverk preset (most continuous
+ * parameters are stored as a floating point number in the range [0..1]) and the physical units used
+ * by the ConvertWithMoss model (envelope times in seconds, filter cut-off in Hertz).
+ *
+ * The Tonverk firmware uses internal, non-published curves to map these normalized values to times
+ * and frequencies. Since those curves are not contained in the preset files, the mappings below are
+ * documented approximations: a power curve for times and an exponential curve for the filter cut-off.
+ * They are chosen so that a Tonverk-to-Tonverk round-trip is loss-less (the inverse functions are
+ * exact), while a conversion to/from a unit-based format (e.g. seconds) is a close approximation. All
+ * range constants are gathered here so they can be tuned in one place.
+ *
+ * @author Jürgen Moßgraber
+ */
+public final class TonverkValues
+{
+ /** Maximum attack time in seconds (normalized value 1.0). */
+ private static final double ATTACK_MAX_SECONDS = 8.0;
+ /** Maximum hold time in seconds (normalized value 1.0). */
+ private static final double HOLD_MAX_SECONDS = 8.0;
+ /** Maximum decay time in seconds (normalized value 1.0). */
+ private static final double DECAY_MAX_SECONDS = 24.0;
+ /** Maximum release time in seconds (normalized value 1.0). */
+ private static final double RELEASE_MAX_SECONDS = 24.0;
+ /** Maximum delay time in seconds (normalized value 1.0). */
+ private static final double DELAY_MAX_SECONDS = 4.0;
+ /**
+ * The exponent of the power curve used for all envelope times. A value > 1 gives finer control
+ * for short times. seconds = max * normalized^curve.
+ */
+ private static final double TIME_CURVE = 3.0;
+
+ /** Minimum filter cut-off frequency in Hertz (normalized value 0.0). */
+ private static final double MIN_CUTOFF_HZ = 20.0;
+ /** Maximum filter cut-off frequency in Hertz (normalized value 1.0). */
+ private static final double MAX_CUTOFF_HZ = 20000.0;
+
+
+ private TonverkValues ()
+ {
+ // Utility class
+ }
+
+
+ /**
+ * Convert a normalized envelope delay value to seconds.
+ *
+ * @param normalized The normalized value [0..1]
+ * @return The time in seconds
+ */
+ public static double normalizedToDelayTime (final double normalized)
+ {
+ return normalizedToTime (normalized, DELAY_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert an envelope delay time in seconds to a normalized value.
+ *
+ * @param seconds The time in seconds
+ * @return The normalized value [0..1]
+ */
+ public static double delayTimeToNormalized (final double seconds)
+ {
+ return timeToNormalized (seconds, DELAY_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert a normalized envelope attack value to seconds.
+ *
+ * @param normalized The normalized value [0..1]
+ * @return The time in seconds
+ */
+ public static double normalizedToAttackTime (final double normalized)
+ {
+ return normalizedToTime (normalized, ATTACK_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert an envelope attack time in seconds to a normalized value.
+ *
+ * @param seconds The time in seconds
+ * @return The normalized value [0..1]
+ */
+ public static double attackTimeToNormalized (final double seconds)
+ {
+ return timeToNormalized (seconds, ATTACK_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert a normalized envelope hold value to seconds.
+ *
+ * @param normalized The normalized value [0..1]
+ * @return The time in seconds
+ */
+ public static double normalizedToHoldTime (final double normalized)
+ {
+ return normalizedToTime (normalized, HOLD_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert an envelope hold time in seconds to a normalized value.
+ *
+ * @param seconds The time in seconds
+ * @return The normalized value [0..1]
+ */
+ public static double holdTimeToNormalized (final double seconds)
+ {
+ return timeToNormalized (seconds, HOLD_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert a normalized envelope decay value to seconds.
+ *
+ * @param normalized The normalized value [0..1]
+ * @return The time in seconds
+ */
+ public static double normalizedToDecayTime (final double normalized)
+ {
+ return normalizedToTime (normalized, DECAY_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert an envelope decay time in seconds to a normalized value.
+ *
+ * @param seconds The time in seconds
+ * @return The normalized value [0..1]
+ */
+ public static double decayTimeToNormalized (final double seconds)
+ {
+ return timeToNormalized (seconds, DECAY_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert a normalized envelope release value to seconds.
+ *
+ * @param normalized The normalized value [0..1]
+ * @return The time in seconds
+ */
+ public static double normalizedToReleaseTime (final double normalized)
+ {
+ return normalizedToTime (normalized, RELEASE_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert an envelope release time in seconds to a normalized value.
+ *
+ * @param seconds The time in seconds
+ * @return The normalized value [0..1]
+ */
+ public static double releaseTimeToNormalized (final double seconds)
+ {
+ return timeToNormalized (seconds, RELEASE_MAX_SECONDS);
+ }
+
+
+ /**
+ * Convert a normalized filter cut-off value to a frequency in Hertz.
+ *
+ * @param normalized The normalized value [0..1]
+ * @return The frequency in Hertz
+ */
+ public static double normalizedToCutoff (final double normalized)
+ {
+ final double clamped = clampNormalized (normalized);
+ return MIN_CUTOFF_HZ * Math.pow (MAX_CUTOFF_HZ / MIN_CUTOFF_HZ, clamped);
+ }
+
+
+ /**
+ * Convert a filter cut-off frequency in Hertz to a normalized value.
+ *
+ * @param hertz The frequency in Hertz
+ * @return The normalized value [0..1]
+ */
+ public static double cutoffToNormalized (final double hertz)
+ {
+ if (hertz <= MIN_CUTOFF_HZ)
+ return 0;
+ if (hertz >= MAX_CUTOFF_HZ)
+ return 1;
+ return Math.log (hertz / MIN_CUTOFF_HZ) / Math.log (MAX_CUTOFF_HZ / MIN_CUTOFF_HZ);
+ }
+
+
+ /**
+ * Clamp a value to the normalized range [0..1].
+ *
+ * @param normalized The value
+ * @return The clamped value
+ */
+ public static double clampNormalized (final double normalized)
+ {
+ return Math.clamp (normalized, 0.0, 1.0);
+ }
+
+
+ private static double normalizedToTime (final double normalized, final double maxSeconds)
+ {
+ return maxSeconds * Math.pow (clampNormalized (normalized), TIME_CURVE);
+ }
+
+
+ private static double timeToNormalized (final double seconds, final double maxSeconds)
+ {
+ if (seconds <= 0)
+ return 0;
+ if (seconds >= maxSeconds)
+ return 1;
+ return Math.pow (seconds / maxSeconds, 1.0 / TIME_CURVE);
+ }
+}
diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/ni/kontakt/AbstractNKIMetadataFileHandler.java b/src/main/java/de/mossgrabers/convertwithmoss/format/ni/kontakt/AbstractNKIMetadataFileHandler.java
index 4de86e66..9bc6b240 100644
--- a/src/main/java/de/mossgrabers/convertwithmoss/format/ni/kontakt/AbstractNKIMetadataFileHandler.java
+++ b/src/main/java/de/mossgrabers/convertwithmoss/format/ni/kontakt/AbstractNKIMetadataFileHandler.java
@@ -1011,6 +1011,9 @@ private void readLoopInformation (final Element zoneElement, final ISampleZone s
loop.setTuning (12.0 * (Math.log (loopTuning) / Math.log (2.0)));
loop.setCrossfadeInSamples (xFadeLength);
loop.setType (loopType);
+ // 'until_release' loops while the key is held and then plays the remainder of the sample
+ // on release (sustain loop); 'until_end' loops continuously
+ loop.setLoopUntilRelease (loopMode.equals (this.tags.untilReleaseValue ()));
sampleMetadata.addLoop (loop);
}
}
diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/renoise/RenoiseCreator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/renoise/RenoiseCreator.java
index ecf58ee4..d90d57b7 100644
--- a/src/main/java/de/mossgrabers/convertwithmoss/format/renoise/RenoiseCreator.java
+++ b/src/main/java/de/mossgrabers/convertwithmoss/format/renoise/RenoiseCreator.java
@@ -195,7 +195,9 @@ private void createSampleElement (final Document document, final Element samples
final List loops = zone.getLoops ();
final ISampleLoop loop = loops.isEmpty () ? null : loops.get (0);
XMLUtils.addTextElement (document, sampleElement, RenoiseTag.LOOP_MODE, loopMode (loop));
- XMLUtils.addTextElement (document, sampleElement, RenoiseTag.LOOP_RELEASE, "false");
+ // 'LoopRelease' true exits the loop on note-off and plays the remainder of the sample
+ // (sustain loop); false keeps looping
+ XMLUtils.addTextElement (document, sampleElement, RenoiseTag.LOOP_RELEASE, loop != null && loop.isLoopUntilRelease () ? "true" : "false");
XMLUtils.addTextElement (document, sampleElement, RenoiseTag.LOOP_START, Integer.toString (loop == null ? 0 : Math.max (0, loop.getStart ())));
XMLUtils.addTextElement (document, sampleElement, RenoiseTag.LOOP_END, Integer.toString (loop == null ? 0 : Math.max (0, loop.getEnd ())));
diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/renoise/RenoiseDetector.java b/src/main/java/de/mossgrabers/convertwithmoss/format/renoise/RenoiseDetector.java
index 7160556d..497f57ee 100644
--- a/src/main/java/de/mossgrabers/convertwithmoss/format/renoise/RenoiseDetector.java
+++ b/src/main/java/de/mossgrabers/convertwithmoss/format/renoise/RenoiseDetector.java
@@ -267,6 +267,9 @@ private ISampleZone parseSample (final File sourceFile, final Element sampleElem
}
loop.setStart (XMLUtils.getChildElementIntegerContent (sampleElement, RenoiseTag.LOOP_START, 0));
loop.setEnd (XMLUtils.getChildElementIntegerContent (sampleElement, RenoiseTag.LOOP_END, zone.getStop ()));
+ // 'LoopRelease' true exits the loop on note-off and plays the remainder of the sample
+ // (sustain loop); false keeps looping
+ loop.setLoopUntilRelease ("true".equalsIgnoreCase (XMLUtils.getChildElementContent (sampleElement, RenoiseTag.LOOP_RELEASE)));
zone.addLoop (loop);
}
diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Creator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Creator.java
index 53416841..80475a55 100644
--- a/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Creator.java
+++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Creator.java
@@ -373,8 +373,10 @@ else if (sampleType == Sf2SampleDescriptor.RIGHT)
instrumentZone.addGenerator (Generator.KEY_RANGE, limitToDefault (sampleZone.getKeyLow (), 0), limitToDefault (sampleZone.getKeyHigh (), 127));
instrumentZone.addGenerator (Generator.VELOCITY_RANGE, limitToDefault (sampleZone.getVelocityLow (), 1), limitToDefault (sampleZone.getVelocityHigh (), 127));
- // Set loop, if any
- instrumentZone.addGenerator (Generator.SAMPLE_MODES, sampleZone.getLoops ().isEmpty () ? 0 : 1);
+ // Set loop, if any: mode 1 loops continuously, mode 3 is a sustain loop (loops until the key
+ // is released and then plays the remainder of the sample)
+ final List sampleLoops = sampleZone.getLoops ();
+ instrumentZone.addGenerator (Generator.SAMPLE_MODES, sampleLoops.isEmpty () ? 0 : (sampleLoops.get (0).isLoopUntilRelease () ? 3 : 1));
// Gain
instrumentZone.addGenerator (Generator.INITIAL_ATTENUATION, (int) Math.round (-sampleZone.getGain () * 10.0));
diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Detector.java b/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Detector.java
index 292db78b..0e5567cd 100644
--- a/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Detector.java
+++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Detector.java
@@ -475,9 +475,13 @@ private static ISampleZone createSampleZone (final Sf2SampleDescriptor sample, f
zone.setStop ((int) (sample.getEnd () - sampleStart + sampleEndOffsetInt));
// Set loop, if any
- if ((generators.getUnsignedValue (Generator.SAMPLE_MODES).intValue () & 1) > 0)
+ final int sampleModes = generators.getUnsignedValue (Generator.SAMPLE_MODES).intValue ();
+ if ((sampleModes & 1) > 0)
{
final ISampleLoop sampleLoop = new DefaultSampleLoop ();
+ // Sample mode 3 keeps looping while the key is held and then plays the remainder of
+ // the sample on release (sustain loop); mode 1 loops continuously
+ sampleLoop.setLoopUntilRelease ((sampleModes & 2) > 0);
final Integer startOffset = generators.getSignedValue (Generator.START_LOOP_ADDRS_OFFSET);
final int startOffsetInt = startOffset == null ? 0 : startOffset.intValue ();
sampleLoop.setStart ((int) (sample.getLoopStart () - sampleStart + startOffsetInt));
diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzCreator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzCreator.java
index 095c58cc..1ea98344 100644
--- a/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzCreator.java
+++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzCreator.java
@@ -395,8 +395,9 @@ private void createLoops (final StringBuilder buffer, final ISampleZone zone)
else
{
final ISampleLoop sampleLoop = loops.get (0);
- // SFZ currently only supports forward looping
- addAttribute (buffer, SfzOpcode.LOOP_MODE, "loop_continuous", false);
+ // 'loop_sustain' loops until the key is released and then plays the remainder of the
+ // sample; 'loop_continuous' keeps looping
+ addAttribute (buffer, SfzOpcode.LOOP_MODE, sampleLoop.isLoopUntilRelease () ? "loop_sustain" : "loop_continuous", false);
final String type = LOOP_TYPE_MAP.get (sampleLoop.getType ());
// No need to write the default value
if (!"forward".equals (type))
diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzDetector.java b/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzDetector.java
index dd5876b3..451204bb 100644
--- a/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzDetector.java
+++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzDetector.java
@@ -621,6 +621,9 @@ private void parseLoop (final ISampleZone sampleMetadata)
return;
case "loop_continuous", "loop_sustain":
+ // 'loop_sustain' loops until the key is released and then plays the remainder of
+ // the sample; 'loop_continuous' keeps looping
+ loop.setLoopUntilRelease ("loop_sustain".equals (loopMode.get ()));
final Optional loopType = this.getAttribute (SfzOpcode.LOOP_TYPE);
if (loopType.isPresent ())
{
diff --git a/src/main/resources/Strings.properties b/src/main/resources/Strings.properties
index 1a207681..26f22914 100644
--- a/src/main/resources/Strings.properties
+++ b/src/main/resources/Strings.properties
@@ -549,6 +549,14 @@ IDS_DS_ADD_FILTER_TO_GROUPS=Add low-pass filter to all groups if none is present
IDS_ELEKTRON_RESAMPLE=Compatibility
IDS_ELEKTRON_CONVERT_TO_24_48=Re-sample to 24bit/48kHz
+IDS_TONVERK_UNKNOWN_MACHINE=Unknown Tonverk generator machine '%1'. Only One-Shot, Multi and Drum are supported.\n
+IDS_TONVERK_SAMPLE_NOT_FOUND=Could not find the sample referenced by the preset: %1\n
+IDS_TONVERK_DRUM_LIMIT=The source has %1 drums but the Tonverk Drum machine only has %2 voices. The additional drums are dropped.\n
+IDS_TONVERK_OUTPUT_ENGINE=Output Engine
+IDS_TONVERK_ENGINE_MULTI=Multi-Sample
+IDS_TONVERK_ENGINE_DRUM=Drum Kit
+IDS_TONVERK_ENGINE_AUTO=Auto (from source)
+
IDS_KMP_OPTIONS=KMP Options
IDS_KMP_USE_KSC=Use KSC files as the input (otherwise only KMP files are used)
IDS_KMP_GAIN_12DB=Enable the +12dB option
diff --git a/src/main/resources/de/mossgrabers/convertwithmoss/templates/tonverk/drum-template.tvpst b/src/main/resources/de/mossgrabers/convertwithmoss/templates/tonverk/drum-template.tvpst
new file mode 100644
index 00000000..b3cd601f
--- /dev/null
+++ b/src/main/resources/de/mossgrabers/convertwithmoss/templates/tonverk/drum-template.tvpst
@@ -0,0 +1,522 @@
+version = 2
+category = 'DRUMS'
+tags = []
+
+[parameters]
+arp_enabled = '0'
+arp_speed = '13'
+arp_note_length = '14'
+arp_mode = '0'
+arp_range = '1'
+lfo1_speed = '32'
+lfo1_multiplier = '1'
+lfo1_destination = ''
+lfo1_depth = '0'
+lfo1_waveform = 'Sine'
+lfo1_start_phase = '0'
+lfo1_trig_mode = 'Free'
+lfo1_smoothing = '0'
+lfo2_speed = '32'
+lfo2_multiplier = '1'
+lfo2_destination = ''
+lfo2_depth = '0'
+lfo2_waveform = 'Random'
+lfo2_start_phase = '0'
+lfo2_trig_mode = 'Hold'
+lfo2_fade = '0'
+lfo2_smoothing = '0'
+pitchbend_value = '0'
+pitchbend_mod1_destination = ''
+pitchbend_mod2_destination = ''
+pitchbend_mod3_destination = ''
+pitchbend_mod4_destination = ''
+pitchbend_mod1_depth = '0'
+pitchbend_mod2_depth = '0'
+pitchbend_mod3_depth = '0'
+pitchbend_mod4_depth = '0'
+aftertouch_value = '0'
+aftertouch_mod1_destination = ''
+aftertouch_mod2_destination = ''
+aftertouch_mod3_destination = ''
+aftertouch_mod4_destination = ''
+aftertouch_mod1_depth = '0'
+aftertouch_mod2_depth = '0'
+aftertouch_mod3_depth = '0'
+aftertouch_mod4_depth = '0'
+modwheel_value = '0'
+modwheel_mod1_destination = ''
+modwheel_mod2_destination = ''
+modwheel_mod3_destination = ''
+modwheel_mod4_destination = ''
+modwheel_mod1_depth = '0'
+modwheel_mod2_depth = '0'
+modwheel_mod3_depth = '0'
+modwheel_mod4_depth = '0'
+breath_control_value = '0'
+breath_control_mod1_destination = ''
+breath_control_mod2_destination = ''
+breath_control_mod3_destination = ''
+breath_control_mod4_destination = ''
+breath_control_mod1_depth = '0'
+breath_control_mod2_depth = '0'
+breath_control_mod3_depth = '0'
+breath_control_mod4_depth = '0'
+fx1_machine = '0'
+fx1_dirtshaper_drive = '0'
+fx1_dirtshaper_rectify = '0.015748031'
+fx1_dirtshaper_hpf = '0.503937'
+fx1_dirtshaper_lpf = '1'
+fx1_dirtshaper_xnoise = '0.1023622'
+fx1_dirtshaper_noise_freq = '0.503937'
+fx1_dirtshaper_noise_reso = '0'
+fx1_dirtshaper_mix = '1'
+fx2_machine = '0'
+fx2_comb_frequency = '0.75'
+fx2_comb_feedback = '0.6640625'
+fx2_comb_damping = '1'
+fx2_comb_mod_start_phase = '-0.0027855153'
+fx2_comb_detune = '0.44085938'
+fx2_comb_mod_rate = '0.27559054'
+fx2_comb_mod_depth = '0.48818898'
+fx2_comb_mix = '0.89'
+midi_machine = '0'
+gen_machine = '2'
+gen_drum_note_priority = '0'
+gen_drum_note_reuse_voices = '1'
+gen_drum_velocity_curve = '3'
+gen_drum_tune = '0.5'
+gen_drum_gain = '1'
+gen_drum_filter_routing = '5'
+gen_drum_voice0_tune = '0.5'
+gen_drum_voice0_play_mode = '1'
+gen_drum_voice0_sample_start = '0'
+gen_drum_voice0_play_length = '1'
+gen_drum_voice0_loop_point = '-0.00008333333'
+gen_drum_voice0_overdrive = '0.24409449'
+gen_drum_voice0_base = '0'
+gen_drum_voice0_width = '1'
+gen_drum_voice0_filter_type = '0.9448819'
+gen_drum_voice0_filter_frequency = '0.19669291'
+gen_drum_voice0_filter_resonance = '0.53543305'
+gen_drum_voice0_filter_env_delay = '0'
+gen_drum_voice0_filter_env_attack = '0'
+gen_drum_voice0_filter_env_decay = '0.42519686'
+gen_drum_voice0_filter_env_sustain = '0'
+gen_drum_voice0_filter_env_release = '0.503937'
+gen_drum_voice0_filter_env_depth = '0.5'
+gen_drum_voice0_filter_env_reset = '0'
+gen_drum_voice0_stereo_spread = '0.5'
+gen_drum_voice0_amp_mode = '1'
+gen_drum_voice0_amp_env_attack = '0.031496063'
+gen_drum_voice0_amp_env_hold = '0.2913386'
+gen_drum_voice0_amp_env_decay = '0.31496063'
+gen_drum_voice0_amp_env_sustain = '1'
+gen_drum_voice0_amp_env_release = '0.2519685'
+gen_drum_voice0_amp_env_reset = '1'
+gen_drum_voice0_volume = '0.78740156'
+gen_drum_voice0_pan = '0.5'
+gen_drum_voice0_lfo1_speed = '0.75'
+gen_drum_voice0_lfo1_multiplier = '1'
+gen_drum_voice0_lfo1_fade = '0.5'
+gen_drum_voice0_lfo1_wave = 'Sine'
+gen_drum_voice0_lfo1_start_phase = '0'
+gen_drum_voice0_lfo1_smoothing = '0'
+gen_drum_voice0_lfo1_trig_mode = 'Free'
+gen_drum_voice0_lfo1_destination = '0'
+gen_drum_voice0_lfo1_depth = '0.5'
+gen_drum_voice0_lfo2_speed = '0.75'
+gen_drum_voice0_lfo2_multiplier = '3'
+gen_drum_voice0_lfo2_fade = '0.5'
+gen_drum_voice0_lfo2_wave = 'Triangle'
+gen_drum_voice0_lfo2_start_phase = '0'
+gen_drum_voice0_lfo2_smoothing = '0'
+gen_drum_voice0_lfo2_trig_mode = 'Free'
+gen_drum_voice0_lfo2_destination = '0'
+gen_drum_voice0_lfo2_depth = '0.5'
+gen_drum_voice0_mod_env_delay = '0'
+gen_drum_voice0_mod_env_attack = '0'
+gen_drum_voice0_mod_env_decay = '0.503937'
+gen_drum_voice0_mod_env_sustain = '0'
+gen_drum_voice0_mod_env_release = '0.503937'
+gen_drum_voice0_mod_env_reset = '0'
+gen_drum_voice0_mod_env_destination = '0'
+gen_drum_voice0_mod_env_depth = '0.5'
+gen_drum_voice1_tune = '0.5'
+gen_drum_voice1_play_mode = '1'
+gen_drum_voice1_sample_start = '0'
+gen_drum_voice1_play_length = '1'
+gen_drum_voice1_loop_point = '-0.00008333333'
+gen_drum_voice1_overdrive = '0.08661418'
+gen_drum_voice1_base = '0'
+gen_drum_voice1_width = '1'
+gen_drum_voice1_filter_type = '0'
+gen_drum_voice1_filter_frequency = '0.91322833'
+gen_drum_voice1_filter_resonance = '0'
+gen_drum_voice1_filter_env_delay = '0'
+gen_drum_voice1_filter_env_attack = '0'
+gen_drum_voice1_filter_env_decay = '0.503937'
+gen_drum_voice1_filter_env_sustain = '0'
+gen_drum_voice1_filter_env_release = '0.503937'
+gen_drum_voice1_filter_env_depth = '0.5'
+gen_drum_voice1_filter_env_reset = '0'
+gen_drum_voice1_stereo_spread = '0.5'
+gen_drum_voice1_amp_mode = '1'
+gen_drum_voice1_amp_env_attack = '0'
+gen_drum_voice1_amp_env_hold = '0'
+gen_drum_voice1_amp_env_decay = '1'
+gen_drum_voice1_amp_env_sustain = '1'
+gen_drum_voice1_amp_env_release = '0.2519685'
+gen_drum_voice1_amp_env_reset = '1'
+gen_drum_voice1_volume = '0.8267717'
+gen_drum_voice1_pan = '0.5'
+gen_drum_voice1_lfo1_speed = '0.75'
+gen_drum_voice1_lfo1_multiplier = '1'
+gen_drum_voice1_lfo1_fade = '0.5'
+gen_drum_voice1_lfo1_wave = 'Random'
+gen_drum_voice1_lfo1_start_phase = '0'
+gen_drum_voice1_lfo1_smoothing = '0'
+gen_drum_voice1_lfo1_trig_mode = 'Hold'
+gen_drum_voice1_lfo1_destination = '0'
+gen_drum_voice1_lfo1_depth = '0.50003904'
+gen_drum_voice1_lfo2_speed = '0.75'
+gen_drum_voice1_lfo2_multiplier = '3'
+gen_drum_voice1_lfo2_fade = '0.5'
+gen_drum_voice1_lfo2_wave = 'Triangle'
+gen_drum_voice1_lfo2_start_phase = '0'
+gen_drum_voice1_lfo2_smoothing = '0'
+gen_drum_voice1_lfo2_trig_mode = 'Free'
+gen_drum_voice1_lfo2_destination = '0'
+gen_drum_voice1_lfo2_depth = '0.5'
+gen_drum_voice1_mod_env_delay = '0'
+gen_drum_voice1_mod_env_attack = '0'
+gen_drum_voice1_mod_env_decay = '0.503937'
+gen_drum_voice1_mod_env_sustain = '0'
+gen_drum_voice1_mod_env_release = '0.503937'
+gen_drum_voice1_mod_env_reset = '0'
+gen_drum_voice1_mod_env_destination = '0'
+gen_drum_voice1_mod_env_depth = '0.5'
+gen_drum_voice2_tune = '0.5'
+gen_drum_voice2_play_mode = '1'
+gen_drum_voice2_sample_start = '0.00066666666'
+gen_drum_voice2_play_length = '1'
+gen_drum_voice2_loop_point = '-0.00008333333'
+gen_drum_voice2_overdrive = '0.87401575'
+gen_drum_voice2_base = '0.46456692'
+gen_drum_voice2_width = '1'
+gen_drum_voice2_filter_type = '0'
+gen_drum_voice2_filter_frequency = '1'
+gen_drum_voice2_filter_resonance = '0'
+gen_drum_voice2_filter_env_delay = '0'
+gen_drum_voice2_filter_env_attack = '0'
+gen_drum_voice2_filter_env_decay = '0.503937'
+gen_drum_voice2_filter_env_sustain = '0'
+gen_drum_voice2_filter_env_release = '0.503937'
+gen_drum_voice2_filter_env_depth = '0.5'
+gen_drum_voice2_filter_env_reset = '0'
+gen_drum_voice2_stereo_spread = '0.6796875'
+gen_drum_voice2_amp_mode = '1'
+gen_drum_voice2_amp_env_attack = '0'
+gen_drum_voice2_amp_env_hold = '1'
+gen_drum_voice2_amp_env_decay = '0.3464567'
+gen_drum_voice2_amp_env_sustain = '1'
+gen_drum_voice2_amp_env_release = '0.2519685'
+gen_drum_voice2_amp_env_reset = '1'
+gen_drum_voice2_volume = '0.72440946'
+gen_drum_voice2_pan = '0.515625'
+gen_drum_voice2_lfo1_speed = '0.75'
+gen_drum_voice2_lfo1_multiplier = '1'
+gen_drum_voice2_lfo1_fade = '0.5'
+gen_drum_voice2_lfo1_wave = 'Sine'
+gen_drum_voice2_lfo1_start_phase = '0'
+gen_drum_voice2_lfo1_smoothing = '0'
+gen_drum_voice2_lfo1_trig_mode = 'Free'
+gen_drum_voice2_lfo1_destination = '0'
+gen_drum_voice2_lfo1_depth = '0.5'
+gen_drum_voice2_lfo2_speed = '0.75'
+gen_drum_voice2_lfo2_multiplier = '3'
+gen_drum_voice2_lfo2_fade = '0.5'
+gen_drum_voice2_lfo2_wave = 'Triangle'
+gen_drum_voice2_lfo2_start_phase = '0'
+gen_drum_voice2_lfo2_smoothing = '0'
+gen_drum_voice2_lfo2_trig_mode = 'Free'
+gen_drum_voice2_lfo2_destination = '0'
+gen_drum_voice2_lfo2_depth = '0.5'
+gen_drum_voice2_mod_env_delay = '0'
+gen_drum_voice2_mod_env_attack = '0'
+gen_drum_voice2_mod_env_decay = '0.503937'
+gen_drum_voice2_mod_env_sustain = '0'
+gen_drum_voice2_mod_env_release = '0.503937'
+gen_drum_voice2_mod_env_reset = '0'
+gen_drum_voice2_mod_env_destination = '0'
+gen_drum_voice2_mod_env_depth = '0.5'
+gen_drum_voice3_tune = '0.5'
+gen_drum_voice3_play_mode = '1'
+gen_drum_voice3_sample_start = '0'
+gen_drum_voice3_play_length = '1'
+gen_drum_voice3_loop_point = '-0.00008333333'
+gen_drum_voice3_overdrive = '0'
+gen_drum_voice3_base = '0'
+gen_drum_voice3_width = '1'
+gen_drum_voice3_filter_type = '0'
+gen_drum_voice3_filter_frequency = '1'
+gen_drum_voice3_filter_resonance = '0'
+gen_drum_voice3_filter_env_delay = '0'
+gen_drum_voice3_filter_env_attack = '0'
+gen_drum_voice3_filter_env_decay = '0.503937'
+gen_drum_voice3_filter_env_sustain = '0'
+gen_drum_voice3_filter_env_release = '0.503937'
+gen_drum_voice3_filter_env_depth = '0.5'
+gen_drum_voice3_filter_env_reset = '0'
+gen_drum_voice3_stereo_spread = '0.5'
+gen_drum_voice3_amp_mode = '1'
+gen_drum_voice3_amp_env_attack = '0'
+gen_drum_voice3_amp_env_hold = '0.03937008'
+gen_drum_voice3_amp_env_decay = '0.1496063'
+gen_drum_voice3_amp_env_sustain = '1'
+gen_drum_voice3_amp_env_release = '0.2519685'
+gen_drum_voice3_amp_env_reset = '1'
+gen_drum_voice3_volume = '0.86614174'
+gen_drum_voice3_pan = '0.5'
+gen_drum_voice3_lfo1_speed = '0.75'
+gen_drum_voice3_lfo1_multiplier = '1'
+gen_drum_voice3_lfo1_fade = '0.5'
+gen_drum_voice3_lfo1_wave = 'Sine'
+gen_drum_voice3_lfo1_start_phase = '0'
+gen_drum_voice3_lfo1_smoothing = '0'
+gen_drum_voice3_lfo1_trig_mode = 'Free'
+gen_drum_voice3_lfo1_destination = '0'
+gen_drum_voice3_lfo1_depth = '0.5'
+gen_drum_voice3_lfo2_speed = '0.75'
+gen_drum_voice3_lfo2_multiplier = '3'
+gen_drum_voice3_lfo2_fade = '0.5'
+gen_drum_voice3_lfo2_wave = 'Triangle'
+gen_drum_voice3_lfo2_start_phase = '0'
+gen_drum_voice3_lfo2_smoothing = '0'
+gen_drum_voice3_lfo2_trig_mode = 'Free'
+gen_drum_voice3_lfo2_destination = '0'
+gen_drum_voice3_lfo2_depth = '0.5'
+gen_drum_voice3_mod_env_delay = '0'
+gen_drum_voice3_mod_env_attack = '0'
+gen_drum_voice3_mod_env_decay = '0.503937'
+gen_drum_voice3_mod_env_sustain = '0'
+gen_drum_voice3_mod_env_release = '0.503937'
+gen_drum_voice3_mod_env_reset = '0'
+gen_drum_voice3_mod_env_destination = '0'
+gen_drum_voice3_mod_env_depth = '0.5'
+gen_drum_voice4_tune = '0.50275'
+gen_drum_voice4_play_mode = '1'
+gen_drum_voice4_sample_start = '0.17074999'
+gen_drum_voice4_play_length = '1'
+gen_drum_voice4_loop_point = '0.66791666'
+gen_drum_voice4_overdrive = '0.7007874'
+gen_drum_voice4_base = '0.43307087'
+gen_drum_voice4_width = '1'
+gen_drum_voice4_filter_type = '0'
+gen_drum_voice4_filter_frequency = '1'
+gen_drum_voice4_filter_resonance = '0'
+gen_drum_voice4_filter_env_delay = '0'
+gen_drum_voice4_filter_env_attack = '0'
+gen_drum_voice4_filter_env_decay = '0.503937'
+gen_drum_voice4_filter_env_sustain = '0'
+gen_drum_voice4_filter_env_release = '0.503937'
+gen_drum_voice4_filter_env_depth = '0.5'
+gen_drum_voice4_filter_env_reset = '0'
+gen_drum_voice4_stereo_spread = '0.4921875'
+gen_drum_voice4_amp_mode = '1'
+gen_drum_voice4_amp_env_attack = '0'
+gen_drum_voice4_amp_env_hold = '1'
+gen_drum_voice4_amp_env_decay = '0.68503934'
+gen_drum_voice4_amp_env_sustain = '1'
+gen_drum_voice4_amp_env_release = '0.2519685'
+gen_drum_voice4_amp_env_reset = '1'
+gen_drum_voice4_volume = '0.77952754'
+gen_drum_voice4_pan = '0.5'
+gen_drum_voice4_lfo1_speed = '0.75'
+gen_drum_voice4_lfo1_multiplier = '1'
+gen_drum_voice4_lfo1_fade = '0.5'
+gen_drum_voice4_lfo1_wave = 'Triangle'
+gen_drum_voice4_lfo1_start_phase = '0'
+gen_drum_voice4_lfo1_smoothing = '0'
+gen_drum_voice4_lfo1_trig_mode = 'Free'
+gen_drum_voice4_lfo1_destination = '19'
+gen_drum_voice4_lfo1_depth = '0.7293359'
+gen_drum_voice4_lfo2_speed = '0.6735156'
+gen_drum_voice4_lfo2_multiplier = '3'
+gen_drum_voice4_lfo2_fade = '0.5'
+gen_drum_voice4_lfo2_wave = 'Sine'
+gen_drum_voice4_lfo2_start_phase = '0'
+gen_drum_voice4_lfo2_smoothing = '0'
+gen_drum_voice4_lfo2_trig_mode = 'Free'
+gen_drum_voice4_lfo2_destination = '3'
+gen_drum_voice4_lfo2_depth = '0.71304685'
+gen_drum_voice4_mod_env_delay = '0'
+gen_drum_voice4_mod_env_attack = '0'
+gen_drum_voice4_mod_env_decay = '0.503937'
+gen_drum_voice4_mod_env_sustain = '0'
+gen_drum_voice4_mod_env_release = '0.503937'
+gen_drum_voice4_mod_env_reset = '0'
+gen_drum_voice4_mod_env_destination = '0'
+gen_drum_voice4_mod_env_depth = '0.5'
+gen_drum_voice5_tune = '0.5'
+gen_drum_voice5_play_mode = '1'
+gen_drum_voice5_sample_start = '0'
+gen_drum_voice5_play_length = '1'
+gen_drum_voice5_loop_point = '-0.00008333333'
+gen_drum_voice5_overdrive = '0'
+gen_drum_voice5_base = '0'
+gen_drum_voice5_width = '1'
+gen_drum_voice5_filter_type = '0'
+gen_drum_voice5_filter_frequency = '1'
+gen_drum_voice5_filter_resonance = '0'
+gen_drum_voice5_filter_env_delay = '0'
+gen_drum_voice5_filter_env_attack = '0'
+gen_drum_voice5_filter_env_decay = '0.503937'
+gen_drum_voice5_filter_env_sustain = '0'
+gen_drum_voice5_filter_env_release = '0.503937'
+gen_drum_voice5_filter_env_depth = '0.5'
+gen_drum_voice5_filter_env_reset = '0'
+gen_drum_voice5_stereo_spread = '0.5'
+gen_drum_voice5_amp_mode = '1'
+gen_drum_voice5_amp_env_attack = '0'
+gen_drum_voice5_amp_env_hold = '0'
+gen_drum_voice5_amp_env_decay = '1'
+gen_drum_voice5_amp_env_sustain = '1'
+gen_drum_voice5_amp_env_release = '0.2519685'
+gen_drum_voice5_amp_env_reset = '1'
+gen_drum_voice5_volume = '0.78740156'
+gen_drum_voice5_pan = '0.5'
+gen_drum_voice5_lfo1_speed = '0.75'
+gen_drum_voice5_lfo1_multiplier = '1'
+gen_drum_voice5_lfo1_fade = '0.5'
+gen_drum_voice5_lfo1_wave = 'Sine'
+gen_drum_voice5_lfo1_start_phase = '0'
+gen_drum_voice5_lfo1_smoothing = '0'
+gen_drum_voice5_lfo1_trig_mode = 'Free'
+gen_drum_voice5_lfo1_destination = '0'
+gen_drum_voice5_lfo1_depth = '0.5'
+gen_drum_voice5_lfo2_speed = '0.75'
+gen_drum_voice5_lfo2_multiplier = '3'
+gen_drum_voice5_lfo2_fade = '0.5'
+gen_drum_voice5_lfo2_wave = 'Triangle'
+gen_drum_voice5_lfo2_start_phase = '0'
+gen_drum_voice5_lfo2_smoothing = '0'
+gen_drum_voice5_lfo2_trig_mode = 'Free'
+gen_drum_voice5_lfo2_destination = '0'
+gen_drum_voice5_lfo2_depth = '0.5'
+gen_drum_voice5_mod_env_delay = '0'
+gen_drum_voice5_mod_env_attack = '0'
+gen_drum_voice5_mod_env_decay = '0.503937'
+gen_drum_voice5_mod_env_sustain = '0'
+gen_drum_voice5_mod_env_release = '0.503937'
+gen_drum_voice5_mod_env_reset = '0'
+gen_drum_voice5_mod_env_destination = '0'
+gen_drum_voice5_mod_env_depth = '0.5'
+gen_drum_voice6_tune = '0.5'
+gen_drum_voice6_play_mode = '1'
+gen_drum_voice6_sample_start = '0'
+gen_drum_voice6_play_length = '1'
+gen_drum_voice6_loop_point = '-0.00008333333'
+gen_drum_voice6_overdrive = '0.08661418'
+gen_drum_voice6_base = '0'
+gen_drum_voice6_width = '1'
+gen_drum_voice6_filter_type = '0'
+gen_drum_voice6_filter_frequency = '1'
+gen_drum_voice6_filter_resonance = '0'
+gen_drum_voice6_filter_env_delay = '0'
+gen_drum_voice6_filter_env_attack = '0'
+gen_drum_voice6_filter_env_decay = '0.503937'
+gen_drum_voice6_filter_env_sustain = '0'
+gen_drum_voice6_filter_env_release = '0.503937'
+gen_drum_voice6_filter_env_depth = '0.5'
+gen_drum_voice6_filter_env_reset = '0'
+gen_drum_voice6_stereo_spread = '0.5'
+gen_drum_voice6_amp_mode = '1'
+gen_drum_voice6_amp_env_attack = '0'
+gen_drum_voice6_amp_env_hold = '0.062992126'
+gen_drum_voice6_amp_env_decay = '0.18897638'
+gen_drum_voice6_amp_env_sustain = '1'
+gen_drum_voice6_amp_env_release = '0.2519685'
+gen_drum_voice6_amp_env_reset = '1'
+gen_drum_voice6_volume = '0.7559055'
+gen_drum_voice6_pan = '0.5'
+gen_drum_voice6_lfo1_speed = '0.625'
+gen_drum_voice6_lfo1_multiplier = '2'
+gen_drum_voice6_lfo1_fade = '0.5'
+gen_drum_voice6_lfo1_wave = 'Triangle'
+gen_drum_voice6_lfo1_start_phase = '0'
+gen_drum_voice6_lfo1_smoothing = '0'
+gen_drum_voice6_lfo1_trig_mode = 'Free'
+gen_drum_voice6_lfo1_destination = '26'
+gen_drum_voice6_lfo1_depth = '0.712539'
+gen_drum_voice6_lfo2_speed = '0.75'
+gen_drum_voice6_lfo2_multiplier = '3'
+gen_drum_voice6_lfo2_fade = '0.5'
+gen_drum_voice6_lfo2_wave = 'Triangle'
+gen_drum_voice6_lfo2_start_phase = '0'
+gen_drum_voice6_lfo2_smoothing = '0'
+gen_drum_voice6_lfo2_trig_mode = 'Free'
+gen_drum_voice6_lfo2_destination = '23'
+gen_drum_voice6_lfo2_depth = '0.54359376'
+gen_drum_voice6_mod_env_delay = '0'
+gen_drum_voice6_mod_env_attack = '0'
+gen_drum_voice6_mod_env_decay = '0.503937'
+gen_drum_voice6_mod_env_sustain = '0'
+gen_drum_voice6_mod_env_release = '0.503937'
+gen_drum_voice6_mod_env_reset = '0'
+gen_drum_voice6_mod_env_destination = '0'
+gen_drum_voice6_mod_env_depth = '0.5'
+gen_drum_voice7_tune = '0.5'
+gen_drum_voice7_play_mode = '1'
+gen_drum_voice7_sample_start = '0'
+gen_drum_voice7_play_length = '1'
+gen_drum_voice7_loop_point = '-0.00008333333'
+gen_drum_voice7_overdrive = '0.14173228'
+gen_drum_voice7_base = '0.6692913'
+gen_drum_voice7_width = '1'
+gen_drum_voice7_filter_type = '0'
+gen_drum_voice7_filter_frequency = '1'
+gen_drum_voice7_filter_resonance = '0'
+gen_drum_voice7_filter_env_delay = '0'
+gen_drum_voice7_filter_env_attack = '0'
+gen_drum_voice7_filter_env_decay = '0.503937'
+gen_drum_voice7_filter_env_sustain = '0'
+gen_drum_voice7_filter_env_release = '0.503937'
+gen_drum_voice7_filter_env_depth = '0.5'
+gen_drum_voice7_filter_env_reset = '0'
+gen_drum_voice7_stereo_spread = '0.5'
+gen_drum_voice7_amp_mode = '1'
+gen_drum_voice7_amp_env_attack = '0'
+gen_drum_voice7_amp_env_hold = '0'
+gen_drum_voice7_amp_env_decay = '1'
+gen_drum_voice7_amp_env_sustain = '1'
+gen_drum_voice7_amp_env_release = '0.2519685'
+gen_drum_voice7_amp_env_reset = '1'
+gen_drum_voice7_volume = '0.7480315'
+gen_drum_voice7_pan = '0.359375'
+gen_drum_voice7_lfo1_speed = '0.75'
+gen_drum_voice7_lfo1_multiplier = '1'
+gen_drum_voice7_lfo1_fade = '0.5'
+gen_drum_voice7_lfo1_wave = 'Sine'
+gen_drum_voice7_lfo1_start_phase = '0'
+gen_drum_voice7_lfo1_smoothing = '0'
+gen_drum_voice7_lfo1_trig_mode = 'Free'
+gen_drum_voice7_lfo1_destination = '0'
+gen_drum_voice7_lfo1_depth = '0.5'
+gen_drum_voice7_lfo2_speed = '0.75'
+gen_drum_voice7_lfo2_multiplier = '3'
+gen_drum_voice7_lfo2_fade = '0.5'
+gen_drum_voice7_lfo2_wave = 'Triangle'
+gen_drum_voice7_lfo2_start_phase = '0'
+gen_drum_voice7_lfo2_smoothing = '0'
+gen_drum_voice7_lfo2_trig_mode = 'Free'
+gen_drum_voice7_lfo2_destination = '0'
+gen_drum_voice7_lfo2_depth = '0.5'
+gen_drum_voice7_mod_env_delay = '0'
+gen_drum_voice7_mod_env_attack = '0'
+gen_drum_voice7_mod_env_decay = '0.503937'
+gen_drum_voice7_mod_env_sustain = '0'
+gen_drum_voice7_mod_env_release = '0.503937'
+gen_drum_voice7_mod_env_reset = '0'
+gen_drum_voice7_mod_env_destination = '0'
+gen_drum_voice7_mod_env_depth = '0.5'
+
diff --git a/src/main/resources/de/mossgrabers/convertwithmoss/templates/tonverk/multi-template.tvpst b/src/main/resources/de/mossgrabers/convertwithmoss/templates/tonverk/multi-template.tvpst
new file mode 100644
index 00000000..cea484f0
--- /dev/null
+++ b/src/main/resources/de/mossgrabers/convertwithmoss/templates/tonverk/multi-template.tvpst
@@ -0,0 +1,146 @@
+version = 2
+category = 'KEYS'
+tags = [
+ 'NOISY',
+ 'PAD',
+ 'VINTAGE',
+]
+
+[parameters]
+arp_enabled = '0'
+arp_speed = '13'
+arp_note_length = '14'
+arp_mode = '0'
+arp_range = '1'
+lfo1_speed = '32'
+lfo1_multiplier = '1'
+lfo1_destination = ''
+lfo1_depth = '0'
+lfo1_waveform = 'Sine'
+lfo1_start_phase = '0'
+lfo1_trig_mode = 'Free'
+lfo1_smoothing = '0'
+lfo2_speed = '32'
+lfo2_multiplier = '3'
+lfo2_destination = ''
+lfo2_depth = '0'
+lfo2_waveform = 'Triangle'
+lfo2_start_phase = '0'
+lfo2_trig_mode = 'Free'
+lfo2_fade = '0'
+lfo2_smoothing = '0'
+pitchbend_value = '0'
+pitchbend_mod1_destination = ''
+pitchbend_mod2_destination = ''
+pitchbend_mod3_destination = ''
+pitchbend_mod4_destination = ''
+pitchbend_mod1_depth = '0'
+pitchbend_mod2_depth = '0'
+pitchbend_mod3_depth = '0'
+pitchbend_mod4_depth = '0'
+aftertouch_value = '0'
+aftertouch_mod1_destination = ''
+aftertouch_mod2_destination = ''
+aftertouch_mod3_destination = ''
+aftertouch_mod4_destination = ''
+aftertouch_mod1_depth = '0'
+aftertouch_mod2_depth = '0'
+aftertouch_mod3_depth = '0'
+aftertouch_mod4_depth = '0'
+modwheel_value = '0'
+modwheel_mod1_destination = ''
+modwheel_mod2_destination = ''
+modwheel_mod3_destination = ''
+modwheel_mod4_destination = ''
+modwheel_mod1_depth = '0'
+modwheel_mod2_depth = '0'
+modwheel_mod3_depth = '0'
+modwheel_mod4_depth = '0'
+breath_control_value = '0'
+breath_control_mod1_destination = ''
+breath_control_mod2_destination = ''
+breath_control_mod3_destination = ''
+breath_control_mod4_destination = ''
+breath_control_mod1_depth = '0'
+breath_control_mod2_depth = '0'
+breath_control_mod3_depth = '0'
+breath_control_mod4_depth = '0'
+fx1_machine = '0'
+fx1_dirtshaper_drive = '0.26771653'
+fx1_dirtshaper_rectify = '0.17322835'
+fx1_dirtshaper_hpf = '0.70866144'
+fx1_dirtshaper_lpf = '0.9448819'
+fx1_dirtshaper_xnoise = '0.047244094'
+fx1_dirtshaper_noise_freq = '0.11811024'
+fx1_dirtshaper_noise_reso = '0.03937008'
+fx1_dirtshaper_mix = '0.29999998'
+fx2_machine = '0'
+fx2_saturator_delay_time = '0.08629921'
+fx2_saturator_delay_mode = '1'
+fx2_saturator_delay_width = '0.8203125'
+fx2_saturator_delay_feedback = '0.3181818'
+fx2_saturator_delay_hp = '0.21259843'
+fx2_saturator_delay_lp = '0.62992126'
+fx2_saturator_delay_mix = '0.19'
+midi_machine = '0'
+gen_machine = '1'
+gen_multi_poly_mode = '0'
+gen_multi_note_priority = '0'
+gen_multi_reuse_voices = '0'
+gen_multi_octave = '0'
+gen_multi_velocity_curve = '3'
+gen_multi_tune = '0.5000833'
+gen_multi_vibrato_depth = '0.03937008'
+gen_multi_vibrato_speed = '0.19685039'
+gen_multi_vibrato_fade = '0.5'
+gen_multi_overdrive = '0.23622048'
+gen_multi_base = '0'
+gen_multi_width = '1'
+gen_multi_filter_type = '0'
+gen_multi_filter_frequency = '0.20023622'
+gen_multi_filter_resonance = '0.031496063'
+gen_multi_filter_env_delay = '0'
+gen_multi_filter_env_attack = '0.41732284'
+gen_multi_filter_env_decay = '0.6535433'
+gen_multi_filter_env_sustain = '0.70866144'
+gen_multi_filter_env_release = '0.6535433'
+gen_multi_filter_env_depth = '0.734375'
+gen_multi_filter_env_reset = '0'
+gen_multi_filter_stereo_spread = '0.5'
+gen_multi_filter_key_tracking = '0.51'
+gen_multi_amp_mode = '2'
+gen_multi_amp_env_attack = '0.4015748'
+gen_multi_amp_env_hold = '0'
+gen_multi_amp_env_decay = '0.503937'
+gen_multi_amp_env_sustain = '1'
+gen_multi_amp_env_release = '0.5826772'
+gen_multi_amp_env_reset = '1'
+gen_multi_volume = '0.6692913'
+gen_multi_pan = '0.5'
+gen_multi_lfo1_speed = '0.75'
+gen_multi_lfo1_multiplier = '1'
+gen_multi_lfo1_fade = '0.5'
+gen_multi_lfo1_wave = 'Sine'
+gen_multi_lfo1_start_phase = '0'
+gen_multi_lfo1_smoothing = '0'
+gen_multi_lfo1_trig_mode = 'Free'
+gen_multi_lfo1_destination = '0'
+gen_multi_lfo1_depth = '0.5'
+gen_multi_lfo2_speed = '0.7710937'
+gen_multi_lfo2_multiplier = '2'
+gen_multi_lfo2_fade = '0.5'
+gen_multi_lfo2_wave = 'Sine'
+gen_multi_lfo2_start_phase = '0'
+gen_multi_lfo2_smoothing = '0'
+gen_multi_lfo2_trig_mode = 'Free'
+gen_multi_lfo2_destination = '8'
+gen_multi_lfo2_depth = '0.5184375'
+gen_multi_mod_env_delay = '0'
+gen_multi_mod_env_attack = '0'
+gen_multi_mod_env_decay = '0.503937'
+gen_multi_mod_env_sustain = '0'
+gen_multi_mod_env_release = '0.503937'
+gen_multi_mod_env_reset = '0'
+gen_multi_mod_env_destination = '0'
+gen_multi_mod_env_depth = '0.5'
+