Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## 19.0.0 (unreleased)

* New: Improved user interface for long lists of formats.
* New: Added support for the Elektron Tonverk preset (TVPST) format - read (One-Shot, Multi and Drum machines, including amplitude and filter envelopes) and write (Multi or Drum machine) (thanks to Douglas Carmichael).
* New: The sustain / 'loop until release' loop mode (the loop runs while the key is held and then plays the remainder of the sample on release, as opposed to a continuous loop) is now preserved between the formats that encode it - SoundFont 2 (sample mode 1/3), SFZ (loop_continuous/loop_sustain), Renoise (LoopRelease), NI Kontakt (read) and Elektron Tonverk/Multi (keep-looping-on-release) - instead of always converting to a continuous loop (thanks to Douglas Carmichael).
* New: Added support for the Polyend Tracker (PTI) instrument format (thanks to Douglas Carmichael).
* New: Added support for the Renoise instrument (XRNI) format (thanks to Douglas Carmichael).
* New: Added support for the Synthstrom Deluge instrument format (thanks to Douglas Carmichael).
Expand Down
28 changes: 26 additions & 2 deletions documentation/README-FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,16 +260,40 @@ There is no write support.
## Elektron Tonverk

The Elektron Tonverk is a dedicated hardware sampler that marks an important milestone for Elektron as its first instrument to support multi-samples. This allows users to map multiple sampled sounds across keys or velocity ranges, creating more expressive and realistic instruments than single-sample playback alone.
Sadly, the elmulti format is very basic and limited. It only supports the basic multi-sample layout does not contain any synthesizer parameters like envelopes or filter settings.

ConvertWithMoss supports two Elektron Tonverk formats: the basic multi-sample mapping files (*.elmulti / *.eldrum) and the full preset (*.tvpst).

### Multi-Sample Mapping (.elmulti / .eldrum)

The elmulti format is very basic and limited. It only supports the basic multi-sample layout and does not contain any synthesizer parameters like envelopes or filter settings.
Furthermore, even this basic setup has some limitations:

* There are no key ranges, the Tonverk always plays the sample with the closest root note. This can lead to different key-ranges than in the source multi-sample.
* Velocity layers are fixed to the key-ranges (like on the modern Akai MPCs).
* Duplicated velocity layers always result in round-robin of these samples (they do not sound at the same time).
* Only 1 Pitch per key zone can be set which means you cannot tune individual samples.

### Destination Options
#### Destination Options

* Re-sample to 24bit/48kHz: If enabled, samples will be resampled to 24bit and 48kHz. While the device can play other resolutions as well, there are reports of issues when you do so.

### Preset (.tvpst)

In contrast to the mapping files, a Tonverk preset is a full sound that also contains the synthesizer parameters. All three generator machines are read:

* **Multi**: a multi-sample mapped to key- and velocity-ranges.
* **One-Shot**: a single sample mapped across the whole keyboard.
* **Drum**: a kit of eight drum voices, each on its own key with its own settings.

The amplitude envelope (AHD or ADSR), the multi-mode filter together with its envelope, the sample loops, gain and panning are converted. The remaining, synthesizer-specific parameters (arpeggiator, effects, global LFOs and the modulation matrix) have no equivalent in the multi-sample model and are therefore not converted.

When writing, the samples are stored next to the preset and referenced by their relative file name, so the preset can be copied anywhere onto the SD card. The full parameter block is created from a neutral factory template (effects bypassed, LFOs, arpeggiator and modulation neutralized) and only the converted parameters above are filled in from the source.

Note: the Tonverk stores envelope times and the filter cut-off frequency as normalized values using internal, non-published curves. ConvertWithMoss uses documented approximations for these. A Tonverk-to-Tonverk conversion is therefore loss-less, while a conversion to or from a unit-based format (such as Waldorf Quantum/Iridium or the Synthstrom Deluge) is a close approximation.

#### Destination Options

* Output Engine: Selects which machine to write. *Multi-Sample* and *Drum Kit* force that machine; *Auto (from source)* writes a Drum machine when the source looks like a drum kit (a percussion category or up to eight single-key zones) and a Multi machine otherwise.
* Re-sample to 24bit/48kHz: If enabled, samples will be resampled to 24bit and 48kHz. While the device can play other resolutions as well, there are reports of issues when you do so.

## Ensoniq EPS/EPS16+/ASR-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@
import de.mossgrabers.convertwithmoss.format.disting.DistingExCreator;
import de.mossgrabers.convertwithmoss.format.disting.DistingExDetector;
import de.mossgrabers.convertwithmoss.format.dls.DlsDetector;
import de.mossgrabers.convertwithmoss.format.elektron.ElektronMultiCreator;
import de.mossgrabers.convertwithmoss.format.elektron.ElektronMultiDetector;
import de.mossgrabers.convertwithmoss.format.elektron.TonverkMultiCreator;
import de.mossgrabers.convertwithmoss.format.elektron.TonverkMultiDetector;
import de.mossgrabers.convertwithmoss.format.elektron.TonverkPresetCreator;
import de.mossgrabers.convertwithmoss.format.elektron.TonverkPresetDetector;
import de.mossgrabers.convertwithmoss.format.ensoniq.epsasr.EnsoniqEpsAsrDetector;
import de.mossgrabers.convertwithmoss.format.ensoniq.mirage.MirageDetector;
import de.mossgrabers.convertwithmoss.format.exs.EXS24Creator;
Expand Down Expand Up @@ -144,7 +146,8 @@ public ConverterBackend (final INotifier notifier)
new DecentSamplerDetector (notifier),
new DlsDetector (notifier),
new DistingExDetector (notifier),
new ElektronMultiDetector (notifier),
new TonverkMultiDetector (notifier),
new TonverkPresetDetector (notifier),
new EnsoniqEpsAsrDetector (notifier),
new MirageDetector (notifier),
new IsoDetector (notifier),
Expand Down Expand Up @@ -179,7 +182,8 @@ public ConverterBackend (final INotifier notifier)
new TX16WxCreator (notifier),
new DecentSamplerCreator (notifier),
new DistingExCreator (notifier),
new ElektronMultiCreator (notifier),
new TonverkMultiCreator (notifier),
new TonverkPresetCreator (notifier),
new KMPCreator (notifier),
new KorgmultisampleCreator (notifier),
new EXS24Creator (notifier),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@ public interface ISampleLoop
void setType (LoopType type);


/**
* Should the loop stop when the note enters the release phase of its amplitude envelope? If
* <code>true</code>, the loop is left at note-off and the remainder of the sample after the loop
* end is played out - a sustain or 'loop until release' loop (e.g. SoundFont sample mode 3, SFZ
* <code>loop_sustain</code>, Renoise <code>LoopRelease</code>=true, Kontakt 'until release'). If
* <code>false</code> (the default), the same loop keeps cycling during the release - a 'loop
* continuous' loop (e.g. SoundFont sample mode 1, SFZ <code>loop_continuous</code>). This
* describes the behavior of this very loop on release, not a separate release-phase loop region.
*
* @return True if the loop stops at note-off and the remainder is played out (sustain loop)
*/
boolean isLoopUntilRelease ();


/**
* Set whether the loop stops when the note enters the release phase of its amplitude envelope.
*
* @param loopUntilRelease True for a sustain / 'until release' loop, false for a continuous loop
*/
void setLoopUntilRelease (boolean loopUntilRelease);


/**
* Get the start of the loop.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
*/
public class DefaultSampleLoop implements ISampleLoop
{
private LoopType loopType = LoopType.FORWARDS;
private int loopStart = -1;
private int loopEnd = -1;
private double tuning = 0;
private double crossfade = 0;
private LoopType loopType = LoopType.FORWARDS;
private int loopStart = -1;
private int loopEnd = -1;
private double tuning = 0;
private double crossfade = 0;
private boolean loopUntilRelease = false;


/** {@inheritDoc} */
Expand All @@ -38,6 +39,22 @@ public void setType (final LoopType type)
}


/** {@inheritDoc} */
@Override
public boolean isLoopUntilRelease ()
{
return this.loopUntilRelease;
}


/** {@inheritDoc} */
@Override
public void setLoopUntilRelease (final boolean loopUntilRelease)
{
this.loopUntilRelease = loopUntilRelease;
}


/** {@inheritDoc} */
@Override
public int getStart ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
import de.mossgrabers.convertwithmoss.file.riff.CommonRiffChunkId;
import de.mossgrabers.convertwithmoss.file.wav.WaveFile;
import de.mossgrabers.convertwithmoss.file.wav.WaveRiffChunkId;
import de.mossgrabers.convertwithmoss.format.elektron.ElektronMultiFile.ElektronKeyZone;
import de.mossgrabers.convertwithmoss.format.elektron.ElektronMultiFile.ElektronSampleSlot;
import de.mossgrabers.convertwithmoss.format.elektron.ElektronMultiFile.ElektronVelocityLayer;
import de.mossgrabers.convertwithmoss.format.elektron.TonverkMultiFile.TonverkKeyZone;
import de.mossgrabers.convertwithmoss.format.elektron.TonverkMultiFile.TonverkSampleSlot;
import de.mossgrabers.convertwithmoss.format.elektron.TonverkMultiFile.TonverkVelocityLayer;
import de.mossgrabers.tools.ui.Functions;


Expand All @@ -38,7 +38,7 @@
*
* @author Jürgen Moßgraber
*/
public class ElektronMultiCreator extends AbstractWavCreator<ElektronMultiCreatorUI>
public class TonverkMultiCreator extends AbstractWavCreator<TonverkMultiCreatorUI>
{
/**
* The factory default velocity. The Tonverk rejects the whole preset file if a velocity layer
Expand Down Expand Up @@ -74,9 +74,9 @@ public class ElektronMultiCreator extends AbstractWavCreator<ElektronMultiCreato
*
* @param notifier The notifier
*/
public ElektronMultiCreator (final INotifier notifier)
public TonverkMultiCreator (final INotifier notifier)
{
super ("Elektron Tonverk", "Emulti", notifier, new ElektronMultiCreatorUI ("Emulti"));
super ("Elektron Tonverk", "Emulti", notifier, new TonverkMultiCreatorUI ("Emulti"));
}


Expand Down Expand Up @@ -112,7 +112,7 @@ public void createPreset (final File destinationFolder, final IMultisampleSource

// Create the preset file - must be done after the samples were written since trimming
// does update the zone/loop positions!
final ElektronMultiFile elektronMulti = createPreset (multisampleSource);
final TonverkMultiFile elektronMulti = createPreset (multisampleSource);
final String presetFile = presetName + ".elmulti";
this.notifier.log ("IDS_NOTIFY_STORING", presetFile);
elektronMulti.write (new File (presetFolder, presetFile).toPath ());
Expand Down Expand Up @@ -167,7 +167,7 @@ protected void rewriteFile (final IMultisampleSource multisampleSource, final IS
* @param multiSampleSource The multi-sample source
* @throws IOException Could not read the audio metadata of a sample
*/
private static void prepareZones (final String presetName, final IMultisampleSource multiSampleSource) throws IOException
static void prepareZones (final String presetName, final IMultisampleSource multiSampleSource) throws IOException
{
for (final Entry<Integer, TreeMap<Integer, List<ISampleZone>>> velocityLayerMapEntry: multiSampleSource.getOrderedSampleZones (false).entrySet ())
{
Expand All @@ -179,7 +179,7 @@ private static void prepareZones (final String presetName, final IMultisampleSou
for (int roundRobinIndex = 0; roundRobinIndex < sampleZones.size (); roundRobinIndex++)
{
final ISampleZone zone = sampleZones.get (roundRobinIndex);
zone.setName (ElektronMultiFile.createSampleName (presetName, velocityLayerIndex, keyRoot, roundRobinIndex));
zone.setName (TonverkMultiFile.createSampleName (presetName, velocityLayerIndex, keyRoot, roundRobinIndex));

final ISampleData sampleData = zone.getSampleData ();
if (sampleData == null)
Expand All @@ -199,14 +199,14 @@ private static void prepareZones (final String presetName, final IMultisampleSou
}


private static ElektronMultiFile createPreset (final IMultisampleSource multiSampleSource)
static TonverkMultiFile createPreset (final IMultisampleSource multiSampleSource)
{
final ElektronMultiFile elektronMulti = new ElektronMultiFile ();
final TonverkMultiFile elektronMulti = new TonverkMultiFile ();
elektronMulti.name = multiSampleSource.getName ();

for (final Entry<Integer, TreeMap<Integer, List<ISampleZone>>> velocityLayerMapEntry: multiSampleSource.getOrderedSampleZones (false).entrySet ())
{
final ElektronKeyZone keyZone = new ElektronKeyZone ();
final TonverkKeyZone keyZone = new TonverkKeyZone ();
elektronMulti.keyZones.add (keyZone);

final int keyRoot = Math.clamp (velocityLayerMapEntry.getKey ().intValue (), 0, 127);
Expand All @@ -217,7 +217,7 @@ private static ElektronMultiFile createPreset (final IMultisampleSource multiSam

for (final Entry<Integer, List<ISampleZone>> sampleZonesEntry: velocityLayerMapEntry.getValue ().entrySet ())
{
final ElektronVelocityLayer velocityLayer = new ElektronVelocityLayer ();
final TonverkVelocityLayer velocityLayer = new TonverkVelocityLayer ();
keyZone.velocityLayers.add (velocityLayer);

// The Tonverk rejects a velocity of exactly 0.0, use the factory default instead
Expand All @@ -226,7 +226,7 @@ private static ElektronMultiFile createPreset (final IMultisampleSource multiSam

for (final ISampleZone sampleZone: sampleZonesEntry.getValue ())
{
final ElektronSampleSlot sampleSlot = new ElektronSampleSlot ();
final TonverkSampleSlot sampleSlot = new TonverkSampleSlot ();
velocityLayer.sampleSlots.add (sampleSlot);

// Must be identical to the file name created in writeSamples!
Expand All @@ -245,9 +245,10 @@ private static ElektronMultiFile createPreset (final IMultisampleSource multiSam
final int crossfade = sampleLoop.getCrossfadeInSamples ();
if (crossfade > 0)
sampleSlot.loopCrossfade = Integer.valueOf (crossfade);
// Continue to loop in the release phase which is the default behavior of
// all source formats
sampleSlot.keepLoopingOnRelease = Boolean.TRUE;
// Keep looping during release unless this is a sustain loop (loop until
// release); continuous looping is the default for source formats without the
// distinction
sampleSlot.keepLoopingOnRelease = Boolean.valueOf (!sampleLoop.isLoopUntilRelease ());
}

if (tuningIsSame)
Expand All @@ -267,7 +268,7 @@ else if (tuning.doubleValue () != sampleZone.getTuning ())
}


private static boolean hasLoop (final ISampleZone zone)
static boolean hasLoop (final ISampleZone zone)
{
final List<ISampleLoop> loops = zone.getLoops ();
if (loops.isEmpty ())
Expand All @@ -287,7 +288,7 @@ private static boolean hasLoop (final ISampleZone zone)
* @param multisampleSource The multi-sample source
* @throws IOException Could not retrieve the current sample rate
*/
private static void recalculateForResample (final IMultisampleSource multisampleSource) throws IOException
static void recalculateForResample (final IMultisampleSource multisampleSource) throws IOException
{
for (final IGroup group: multisampleSource.getGroups ())
for (final ISampleZone zone: group.getSampleZones ())
Expand Down Expand Up @@ -341,7 +342,7 @@ private static void recalculateForResample (final IMultisampleSource multisample
*
* @param zone The zone
*/
private static void clampLoops (final ISampleZone zone)
static void clampLoops (final ISampleZone zone)
{
final int lastIndex = zone.getStop () - 1;
if (lastIndex < 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*
* @author Jürgen Moßgraber
*/
public class ElektronMultiCreatorUI extends WavChunkSettingsUI
public class TonverkMultiCreatorUI extends WavChunkSettingsUI
{
private static final String RESAMPLE_TO_24_48 = "ResampleTo2448";

Expand All @@ -38,7 +38,7 @@ public class ElektronMultiCreatorUI extends WavChunkSettingsUI
*
* @param prefix The prefix to use for the identifier
*/
public ElektronMultiCreatorUI (final String prefix)
public TonverkMultiCreatorUI (final String prefix)
{
// Only the sample chunk is enabled by default: the Tonverk factory WAV files contain only
// 'fmt ', 'data' and 'smpl' chunks and the Tonverk WAV parser is strict
Expand Down
Loading