From aec7e2486d416247a04c21cfcc3f58f49cb79aa6 Mon Sep 17 00:00:00 2001 From: Douglas Carmichael Date: Mon, 14 Sep 2026 15:36:15 -0400 Subject: [PATCH] Waldorf Quantum/Iridium: write up to four layers, the layout of the MK2 generation The 'Use a second layer' check box becomes a 'Maximum number of layers' drop-down with 1, 2, 3 or 4 layers. With 3 or 4, a source with more than 6 groups is written with up to 9 or 12 multi-samples in the layout the Iridium MK2 stores its own patches in: the layer count 2, the format version 15 and always four stored layers, of which the unused ones are switched off (LayerActive 0, no oscillators, no maps). The loader of the device clears the file offsets of the layers 3 and 4 in files up to the version 14, which is why the layout needs the version 15. Only as many layers as the groups need are used, so a source with up to 6 groups is still written as the 2-layer patch of the version 14 which every instrument of the family plays. The samples of all later layers are named in the first layer for the importer of the device. A setting of the old option is taken over, and the old command line parameter is still accepted. Verified on an Iridium MK2 with OS 4.0.6: a written 4-layer patch with one sample pitch per layer plays all four pitches, a 3-layer patch with its switched-off fourth layer plays its three. Claude-Session: https://claude.ai/code/session_01P2bmFi6STyJoKHvjkuLiVy --- documentation/CHANGELOG.md | 1 + documentation/README-FORMATS.md | 4 +- documentation/design/WALDORF_QPAT_FORMAT.md | 22 +++-- .../waldorf/qpat/WaldorfQpatCreator.java | 83 +++++++++++++---- .../waldorf/qpat/WaldorfQpatCreatorUI.java | 92 +++++++++++++++++-- src/main/resources/Strings.properties | 8 +- 6 files changed, 175 insertions(+), 35 deletions(-) diff --git a/documentation/CHANGELOG.md b/documentation/CHANGELOG.md index 8a01c449..2173fc9c 100644 --- a/documentation/CHANGELOG.md +++ b/documentation/CHANGELOG.md @@ -119,6 +119,7 @@ * Fixed: The cutoff of the filter was written with the normalized parameter value as its display text, so the device listed a patch with a cutoff of e.g. 61.7 Hz as '0.2592 Hz'. A cutoff at the top of the range of the model (20,000 Hz) also wrote a parameter value slightly above the 1.0 the device expects; it is now limited to the 19,912.2 Hz of the device. * New: The volume and the pan of the whole timbre (TBPIVolume, an attenuation in dB, and TBPIPan, -63..63) are read and applied on top of the settings of the partials; a timbre which the editor turned down - by up to 16 dB in the Studio G Dallas library - was converted at full level. * Waldorf Quantum/Iridium + * New: The 'Use a second layer' option became 'Maximum number of layers' with 1 to 4 layers. With 3 or 4 layers a source with more than 6 groups is written with up to 9 or 12 multi-samples in the layout of the MK2 generation: the layer count 2, the format version 15 and always four stored layers, of which the unused ones are switched off, exactly as the Iridium MK2 stores its own patches (304 files of the corpus). The loader of the device clears the file offsets of the layers 3 and 4 in files up to the version 14, which is why four layers need the version 15; the first generation of the instruments holds two layers (its manuals) and cannot play such a file. Verified on an Iridium MK2 (OS 4.0.6): a written 4-layer patch with one sample pitch per layer plays all four pitches, a 3-layer patch with its switched-off fourth layer plays its three. Only as many layers as the groups need are used, so a source with up to 6 groups is still written as the 2-layer patch which every instrument of the family plays. The samples of all later layers are named in the first layer for the importer of the device. A setting of the old option is taken over. * New: Round robins are written into one sample map. Entries of a map which overlap in key and velocity alternate on successive notes on the device, which is exactly a round robin; the zones of a round robin were spread over separate oscillators as if they were stacked layers, so all of them sounded at once on every note. When reading, overlapping entries of a map become a round robin. The device has no random selection, so random zones alternate as well. * New: A monophonic source is written with the mono mode of the patch (PolyMonoMode), which is read back as well. * New: The key window of an oscillator (Osc{i}MinNote/MaxNote) is applied when reading: a zone outside of it is silent on the device and is dropped, a zone across its edge is cut at it. diff --git a/documentation/README-FORMATS.md b/documentation/README-FORMATS.md index deff8ef1..dd1aee34 100644 --- a/documentation/README-FORMATS.md +++ b/documentation/README-FORMATS.md @@ -1015,7 +1015,7 @@ Writing creates such a folder, which only needs to be copied to the device. Note This family of Waldorf synthesizers supports the playback of multi-samples. One preset can contain several layers - 2 on the first generation of the instruments and 4 on the MK2 generation (Iridium MK2, Quantum MK2 and instruments upgraded to MK2 hardware). A layer is a complete preset in itself and the layers are simply concatenated. Each layer can have up to 3 oscillators of which each oscillator can contain its own multi-sample. The format is not documented by Waldorf; the file layout, the sample map and the value laws which are read and written are described in *documentation/design/WALDORF_QPAT_FORMAT.md*. -If this format is used as the source it produces one output preset per layer which plays samples; a layer which is switched off, or which plays a wave-table instead of samples, is reported and skipped. If used as the destination format, each group of the source multi-sample is applied to one of the 3 oscillators of a layer; with the *Use a second layer* option a preset holds up to 6 multi-samples instead of 3, and all groups which do not fit are added to the multi-sample of the last oscillator, as before. Note that the device cannot select a layer by velocity - it splits its layers by key, by MIDI channel or cycles them - so a velocity split is always written into the sample maps of one layer and only sounds which are meant to be heard together are spread across the layers. A round robin stays inside one sample map as well: entries of a map which overlap in key and velocity alternate on successive notes on the device, which is how it plays a round robin, and overlapping entries are read back as one (the device has no random selection, so random zones alternate too). A monophonic source is written with the mono mode of the patch. +If this format is used as the source it produces one output preset per layer which plays samples; a layer which is switched off, or which plays a wave-table instead of samples, is reported and skipped. If used as the destination format, each group of the source multi-sample is applied to one of the 3 oscillators of a layer; with the *Layers* option a preset holds up to 6 multi-samples on 2 layers or up to 9 or 12 on 3 or 4 layers of the MK2 generation instead of 3, and all groups which do not fit are added to the multi-sample of the last oscillator, as before. Note that the device cannot select a layer by velocity - it splits its layers by key, by MIDI channel or cycles them - so a velocity split is always written into the sample maps of one layer and only sounds which are meant to be heard together are spread across the layers. A round robin stays inside one sample map as well: entries of a map which overlap in key and velocity alternate on successive notes on the device, which is how it plays a round robin, and overlapping entries are read back as one (the device has no random selection, so random zones alternate too). A monophonic source is written with the mono mode of the patch. The volume, panning and tuning of an oscillator are offsets on top of the values of its sample map entries, therefore the two are combined when reading instead of the oscillator replacing the sample map. When writing, the volume and panning of a group are stored on its oscillator and only the remainder in its sample map, so they survive a Quantum/Iridium round-trip - previously the oscillator was always written as 0 dB and Center. The volume, gain and panning of a layer are applied on top of its oscillators when reading. The importer of the device copies only the samples which the first layer names, so the samples of a second layer are named in a map of the first layer as well, in entries which never play. @@ -1029,7 +1029,7 @@ Since the device has a field of its own for the bank, a source which carries its * Author: Written into the preset's Author field, which the device shows and can group presets by. When left empty, the creator from the source metadata is kept (e.g. the sound designer stored in a SoundFont). * Bank: Written into the preset's Bank field. When left empty, the description from the source metadata is kept. Both the Author and the Bank only take as much text as the device shows completely: its *Load Patch* page shows the name, the bank and the author of the selected preset in a narrow column and leaves out every character which does not fit - e.g. of the bank 'Digital Collection Vintage' only 'Digital Collection Vinta' is visible. The font is proportional, so how many characters fit depends on the characters: on average 17 capital letters or 20 lower-case ones, but e.g. 11 of the letter 'm'. On the command line (QPATAuthor / QPATBank) a value which is too wide is reported, together with the part which the device would show, and nothing is converted. * Prefix file names with an import number: Prefixes each written preset file with a 5-digit number (e.g. *05002-Name.qpat*), which mirrors the naming of the device's own preset export; on import the device assigns the preset to that number. The *First import number* is used for the first written preset and each further preset increases the number by one; the source presets are converted in alphabetical order. -* Use a second layer for more than 3 groups: A preset normally holds one layer with up to 3 multi-samples, one on each oscillator, and every further group of the source is merged into the third of them. With this option a source with more than 3 groups is written as 2 layers with up to 6 multi-samples, which keeps the groups apart and gives the second set of them its own filter and amplitude envelope. Two layers are stored the same way by every instrument of the family, so such a preset plays on a Quantum, an Iridium and an Iridium Core as well as on the MK2 generation. Only groups which sound at the same time are put into a separate layer - the device cannot select a layer by velocity, so a velocity split stays inside the sample maps. Disabled by default. Verified on an Iridium MK2: the written preset loads and its second layer carries its own filter and amplitude envelope. The device imports only the samples which the first layer names, so the samples of the second layer are named in the last sample map of the first layer as well, in entries which never play. Verified on an Iridium MK2 (OS 4.0.6): a test preset whose second layer brings a sample of its own plays both layers after the import, while the same preset without the entries fails to load that sample. +* Maximum number of layers: A preset normally holds one layer with up to 3 multi-samples, one on each oscillator, and every further group of the source is merged into the third of them. With 2 layers a source with more than 3 groups is written as 2 layers with up to 6 multi-samples, which keeps the groups apart and gives the second set of them its own filter and amplitude envelope; with 3 or 4 layers a source with more than 6 groups is written with up to 9 or 12 multi-samples - 3 leaves the fourth layer of an MK2 free for a sound of your own. Only as many layers as the groups need are used. Two layers are stored the same way by every instrument of the family, so such a preset plays on a Quantum, an Iridium and an Iridium Core as well as on the MK2 generation; a preset with 3 or 4 layers uses the layout of the MK2 generation (format version 15, four stored layers of which the unused ones are switched off), which only the Iridium MK2, the Quantum MK2 and first-generation instruments upgraded with the MK2 hardware can play - the first generation holds two layers. Only groups which sound at the same time are put into a separate layer - the device cannot select a layer by velocity, so a velocity split stays inside the sample maps. The default is 1 layer. Verified on an Iridium MK2: a written 2-layer preset loads and its second layer carries its own filter and amplitude envelope. The device imports only the samples which the first layer names, so the samples of the later layers are named in the last sample map of the first layer as well, in entries which never play - verified on an Iridium MK2 (OS 4.0.6): a test preset whose second layer brings a sample of its own plays both layers after the import, while the same preset without the entries fails to load that sample. Verified on an Iridium MK2 (OS 4.0.6) as well: a written 4-layer preset with one sample pitch per layer plays all four pitches, and a 3-layer preset with its switched-off fourth layer plays its three. * Shorten file names to the displayed preset name: Names the written file after the preset name which the device displays instead of the full name of the source. A preset of a bank otherwise carries its bank in the file name (e.g. *05000-Full Arco String - Arco Strings Lo.qpat*, 45 characters), which is more than the import screen of the device shows - measured on an Iridium MK2, its file list displays about 43 characters and cuts off the rest, exactly the end that tells the presets apart. With this option the example becomes *05000-Arco Strings Lo.qpat*, and the whole file name - including the import number prefix and the file ending - is kept within 40 characters. Disabled by default, since the full name is what tells the presets of different banks apart in a file browser on the computer. * Options to write/update [WAV Chunk Information](#wav-chunk-information) diff --git a/documentation/design/WALDORF_QPAT_FORMAT.md b/documentation/design/WALDORF_QPAT_FORMAT.md index e96179dd..9dde25aa 100644 --- a/documentation/design/WALDORF_QPAT_FORMAT.md +++ b/documentation/design/WALDORF_QPAT_FORMAT.md @@ -750,7 +750,12 @@ brings a sample of its own plays both layers after the import, while the same pa entry fails with *loading samples/.../L2_saw_octave.wav failed*. A written two-layer patch loads and its second layer carries its own filter and amplitude envelope -**(hw)**; the samples of the second layer were the open problem described above. +**(hw)**; the samples of the second layer were the open problem described above. ConvertWithMoss +writes the four-layer layout of the MK2 - count 2, version 15, four stored layers, the unused ones +with `LayerActive` 0 and without oscillators - when a source needs three or four layers. Verified +on an Iridium MK2 with OS 4.0.6 **(hw)**: a written four-layer patch with one sample pitch per +layer plays all four pitches after the import, and a three-layer patch with its switched-off +fourth layer plays its three. ## 8. Reading a patch @@ -816,7 +821,12 @@ A written two-layer patch loads and its second layer carries its own filter and whether an MK2 can write code 1, and whether a first-generation instrument accepts code 2, is unknown. The loader accepts the codes 1, 2 and 3 as "more than one layer" and loads the layers 3 and 4 for the codes 2 and 3 (fw). A written two-layer patch loads and, with the entries of - section 7.5, plays its own samples in the second layer (hw). + section 7.5, plays its own samples in the second layer; a written four-layer patch (count 2, + version 15) plays all four layers on an Iridium MK2 (hw). The first generation holds two layers - + the Iridium and Iridium Core manuals switch "between the two layers by tapping on the Layer 1 or + Layer 2 button", and Waldorf describes the MK2 upgrade as raising the layer count from 2 to 4 - + so a count-2 file is for the MK2 generation only; how a first-generation OS reacts to one is not + known (manual). * **Oscillator type indices.** *Off* is 5 in versions 9-11 and 6 in version 15; how an instrument interprets the index of a file of another version (e.g. a version-14 file with `Osc2Type` = 5 on an MK2) is untested - hence the advice to write nothing for unused oscillators. @@ -870,7 +880,7 @@ For readers who want to compare an implementation against ConvertWithMoss (`Wald Experimental*). * **Options**: re-sample to 16 bit / 44.1 kHz (default on), *Author* and *Bank* overrides, the `NNNNN-` import number prefix with a configurable first number, short file names which keep the - whole file name within 40 characters, and *Use a second layer for more than 3 groups* (off by - default, which writes exactly what earlier versions wrote). It writes at most two layers - the - layer count 1, which every instrument of the family has stored since the format version 8 - and - never the four-layer count, which has only been seen in files of the version 15. + whole file name within 40 characters, and *Maximum number of layers* with 1 to 4 layers (1 by default, + which writes exactly what earlier versions wrote). One or two layers use the layer count 1, which + every instrument of the family has stored since the format version 8; three or four use the + four-layer layout of section 7.5 with the format version 15. diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatCreator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatCreator.java index e98f0cbf..844c230a 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatCreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatCreator.java @@ -80,7 +80,14 @@ public class WaldorfQpatCreator extends AbstractWavCreator /** The sample rate which the device plays and to which this creator re-samples. */ private static final int DESTINATION_SAMPLE_RATE = 44100; + /** The format version of a patch with one or two layers, which every instrument of the family loads. */ private static final int PRESET_VERSION = 14; + /** + * The format version of a patch with the four-layer layout. The loader clears the file offsets + * of the layers 3 and 4 in files up to the version 14, so four layers need the version of the + * MK2 generation. + */ + private static final int PRESET_VERSION_FOUR = 15; /** The size of the header of a patch, which every layer of a patch has as well. */ private static final int HEADER_SIZE = 512; @@ -99,14 +106,19 @@ public class WaldorfQpatCreator extends AbstractWavCreator */ private static final int MAX_MAP_ENTRIES = 1024; /** - * The maximum number of layers which is written. The MK2 generation of the instruments stores - * four layers, but that layer count has only ever been observed in files of the format version - * 15, while two layers are stored the same way from the version 8 on - so a patch with two - * layers plays on every instrument of the family. + * The maximum number of layers which is written. Two layers are stored the same way from the + * format version 8 on, so a patch with two layers plays on every instrument of the family. Four + * layers are the layout of the MK2 generation (Iridium MK2, Quantum MK2 and first-generation + * instruments upgraded to the MK2 hardware): the layer count 2, the format version 15 and + * always four stored layers, of which the unused ones are switched off. */ - private static final int MAX_LAYERS = 2; + private static final int MAX_LAYERS = 4; + /** The number of layers which a patch with three or four layers stores. */ + private static final int MAX_LAYERS_TWO = 2; /** Layer count: two layers, the file offset of the 2nd one is stored at 432. */ private static final int LAYER_COUNT_TWO = 1; + /** Layer count: four layers, the file offsets of the layers 2, 3 and 4 are stored at 432, 440 and 444. */ + private static final int LAYER_COUNT_FOUR = 2; /** The header holds the file offsets of the layers 2, 3 and 4. */ private static final int NUM_LAYER_OFFSETS = 3; /** @@ -407,15 +419,33 @@ private void storeMultisample (final IMultisampleSource multisampleSource, final metadata.setDescription (bank); final int numLayers = layers.size (); - final int layerCount = numLayers == 1 ? 0 : LAYER_COUNT_TWO; + // One or two layers are stored the way every instrument of the family stores them; three + // or four layers need the layout of the MK2 generation, which always stores four layers - + // the loader checks the offsets of all of them - and switches the unused ones off + final boolean fourLayerLayout = numLayers > MAX_LAYERS_TWO; + final int layerCount = numLayers == 1 ? 0 : fourLayerLayout ? LAYER_COUNT_FOUR : LAYER_COUNT_TWO; + final int version = fourLayerLayout ? PRESET_VERSION_FOUR : PRESET_VERSION; + final int numStoredLayers = fourLayerLayout ? MAX_LAYERS : numLayers; + if (fourLayerLayout) + this.notifier.log ("IDS_QPAT_NOTIFY_FOUR_LAYERS", Integer.toString (numLayers)); // The content of every layer has to be known before the first one can be written, since // the header holds the file offset of the following layer final List> layerParameters = new ArrayList<> (); final List> layerSampleMaps = new ArrayList<> (); - final int [] layerSizes = new int [numLayers]; - for (int i = 0; i < numLayers; i++) + final int [] layerSizes = new int [numStoredLayers]; + for (int i = 0; i < numStoredLayers; i++) { + if (i >= numLayers) + { + // A stored layer which is not used: switched off, without oscillators and maps + final List parameters = createInactiveLayerParameters (); + layerParameters.add (parameters); + layerSampleMaps.add (new ArrayList<> ()); + layerSizes[i] = HEADER_SIZE + parameters.size () * PARAMETER_SIZE; + continue; + } + final List groups = layers.get (i); // A zero-attack/zero-decay amplitude envelope that sustains below full level makes the // device pop at the start of each note: it snaps to the 100% attack peak and then @@ -438,18 +468,37 @@ private void storeMultisample (final IMultisampleSource multisampleSource, final layerSizes[i] = size; } - // The absolute file offsets of the layers 2, 3 and 4; a layer which is not stored keeps 0 + // The absolute file offsets of the layers 2, 3 and 4, each the sum of the sizes of the + // layers in front of it; a layer which is not stored keeps 0 final int [] layerOffsets = new int [NUM_LAYER_OFFSETS]; - System.arraycopy (layerSizes, 0, layerOffsets, 0, numLayers - 1); + for (int i = 1; i < numStoredLayers; i++) + layerOffsets[i - 1] = layerOffsets[Math.max (0, i - 2)] + layerSizes[i - 1]; try (final FileOutputStream out = new FileOutputStream (multiFile)) { - for (int i = 0; i < numLayers; i++) - writeLayer (out, metadata, deviceName, layerParameters.get (i), layerSampleMaps.get (i), layerCount, numLayers == 1 ? 0 : (int) TIMBRE_MODE_MULTI, layerOffsets); + for (int i = 0; i < numStoredLayers; i++) + writeLayer (out, metadata, deviceName, version, layerParameters.get (i), layerSampleMaps.get (i), layerCount, numLayers == 1 ? 0 : (int) TIMBRE_MODE_MULTI, layerOffsets); } } + /** + * Create the parameters of a stored layer which is not used. The four-layer layout always + * stores four layers, so a patch with three sounding layers stores a fourth one which is + * switched off; its oscillators are left unwritten, which the device reads as Off. + * + * @return The parameters + */ + private static List createInactiveLayerParameters () + { + final List parameters = new ArrayList<> (); + parameters.add (new WaldorfQpatParameter ("TimbreMode", "Layered", TIMBRE_MODE_MULTI)); + parameters.add (new WaldorfQpatParameter ("MultiAllocMode", "Layered", 0)); + parameters.add (new WaldorfQpatParameter ("LayerActive", "Off", 0)); + return parameters; + } + + /** * Write one layer of the patch: its header, its parameters and its sample maps. A layer is a * complete patch of its own; the layers of a patch are simply stored one after the other. @@ -457,6 +506,7 @@ private void storeMultisample (final IMultisampleSource multisampleSource, final * @param out The output stream to write to * @param metadata The metadata of the multi-sample * @param deviceName The name to write into the name field, which the device displays + * @param version The format version of the patch * @param parameters The parameters of the layer * @param sampleMaps The sample maps of the layer * @param layerCount The number of layers of the patch, coded as the device does @@ -464,9 +514,9 @@ private void storeMultisample (final IMultisampleSource multisampleSource, final * @param layerOffsets The absolute file offsets of the layers 2, 3 and 4 * @throws IOException Could not write the layer */ - private static void writeLayer (final OutputStream out, final IMetadata metadata, final String deviceName, final List parameters, final List sampleMaps, final int layerCount, final int timbreMode, final int [] layerOffsets) throws IOException + private static void writeLayer (final OutputStream out, final IMetadata metadata, final String deviceName, final int version, final List parameters, final List sampleMaps, final int layerCount, final int timbreMode, final int [] layerOffsets) throws IOException { - writeHeader (out, metadata, deviceName); + writeHeader (out, metadata, deviceName, version); StreamUtils.writeUnsigned16 (out, parameters.size (), false); StreamUtils.padBytes (out, 2); @@ -1466,12 +1516,13 @@ else if (releaseSlope < 0) * @param out The output stream to write to * @param metadata The metadata * @param name The name of the multi-sample + * @param version The format version of the patch * @throws IOException Could not write */ - private static void writeHeader (final OutputStream out, final IMetadata metadata, final String name) throws IOException + private static void writeHeader (final OutputStream out, final IMetadata metadata, final String name, final int version) throws IOException { StreamUtils.writeUnsigned32 (out, WaldorfQpatConstants.MAGIC, false); - StreamUtils.writeUnsigned32 (out, PRESET_VERSION, false); + StreamUtils.writeUnsigned32 (out, version, false); StreamUtils.writeAscii (out, StringUtils.fixASCII (name), WaldorfQpatConstants.MAX_STRING_LENGTH); // The author (offset 40) and bank (offset 72) fields are shown by the device. Use the // explicit creator settings when provided, otherwise fall back to the source metadata. diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatCreatorUI.java b/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatCreatorUI.java index a6d6cea2..ec920d7f 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatCreatorUI.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatCreatorUI.java @@ -18,6 +18,7 @@ import de.mossgrabers.tools.ui.panel.BoxPanel; import javafx.geometry.Orientation; import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; import javafx.scene.control.TextField; import javafx.scene.control.TextFormatter; import javafx.scene.control.Tooltip; @@ -37,7 +38,17 @@ public class WaldorfQpatCreatorUI extends WavChunkSettingsUI private static final String QPAT_NUMBER_PREFIX = "QPATNumberPrefix"; private static final String QPAT_SHORT_FILE_NAMES = "QPATShortFileNames"; private static final String QPAT_NUMBER_PREFIX_START = "QPATNumberPrefixStart"; + private static final String QPAT_LAYERS = "QPATLayers"; + /** The option which the layers option replaced, still read so that its setting is taken over. */ private static final String QPAT_SECOND_LAYER = "QPATUseSecondLayer"; + /** The choices of the layers option: the maximum number of layers of a patch. */ + private static final int [] LAYER_OPTIONS = + { + 1, + 2, + 3, + 4 + }; private CheckBox limitTo16441CheckBox; private TextField authorField; @@ -45,14 +56,14 @@ public class WaldorfQpatCreatorUI extends WavChunkSettingsUI private CheckBox numberPrefixCheckBox; private TextField numberPrefixStartField; private CheckBox shortFileNamesCheckBox; - private CheckBox secondLayerCheckBox; + private ComboBox layersBox; private boolean limitTo16441; private String author = ""; private String bank = ""; private boolean numberPrefix; private int numberPrefixStart = 0; private boolean shortFileNames; - private boolean useSecondLayer; + private int maximumLayers = 1; /** @@ -82,7 +93,7 @@ public Pane getEditPane () this.numberPrefixStartField = panel.createPositiveIntegerField ("@IDS_QPAT_NUMBER_PREFIX_START"); this.numberPrefixStartField.disableProperty ().bind (this.numberPrefixCheckBox.selectedProperty ().not ()); this.shortFileNamesCheckBox = panel.createCheckBox ("@IDS_QPAT_SHORT_FILE_NAMES"); - this.secondLayerCheckBox = panel.createCheckBox ("@IDS_QPAT_SECOND_LAYER"); + this.layersBox = panel.createComboBox ("@IDS_QPAT_LAYERS", List.of (Functions.getText ("@IDS_QPAT_LAYERS_ONE"), Functions.getText ("@IDS_QPAT_LAYERS_TWO"), Functions.getText ("@IDS_QPAT_LAYERS_THREE"), Functions.getText ("@IDS_QPAT_LAYERS_FOUR"))); final TitledSeparator separator = this.addWavChunkOptions (panel); separator.getStyleClass ().add ("titled-separator-pane"); @@ -100,7 +111,9 @@ public void loadSettings (final BasicConfig config) this.numberPrefixCheckBox.setSelected (config.getBoolean (QPAT_NUMBER_PREFIX, false)); this.numberPrefixStartField.setText (Integer.toString (config.getInteger (QPAT_NUMBER_PREFIX_START, 0))); this.shortFileNamesCheckBox.setSelected (config.getBoolean (QPAT_SHORT_FILE_NAMES, false)); - this.secondLayerCheckBox.setSelected (config.getBoolean (QPAT_SECOND_LAYER, false)); + // The setting of the option which this one replaced is taken over + final int layers = config.getInteger (QPAT_LAYERS, config.getBoolean (QPAT_SECOND_LAYER, false) ? 2 : 1); + this.layersBox.getSelectionModel ().select (layersToIndex (layers)); super.loadSettings (config); } @@ -116,7 +129,7 @@ public void saveSettings (final BasicConfig config) config.setBoolean (QPAT_NUMBER_PREFIX, this.numberPrefixCheckBox.isSelected ()); config.setInteger (QPAT_NUMBER_PREFIX_START, this.parseNumberPrefixStart ()); config.setBoolean (QPAT_SHORT_FILE_NAMES, this.shortFileNamesCheckBox.isSelected ()); - config.setBoolean (QPAT_SECOND_LAYER, this.secondLayerCheckBox.isSelected ()); + config.setInteger (QPAT_LAYERS, LAYER_OPTIONS[selectedLayerIndex (this.layersBox)]); super.saveSettings (config); } @@ -135,7 +148,7 @@ public boolean checkSettingsUI (final INotifier notifier) this.numberPrefix = this.numberPrefixCheckBox.isSelected (); this.numberPrefixStart = this.parseNumberPrefixStart (); this.shortFileNames = this.shortFileNamesCheckBox.isSelected (); - this.useSecondLayer = this.secondLayerCheckBox.isSelected (); + this.maximumLayers = LAYER_OPTIONS[selectedLayerIndex (this.layersBox)]; return true; } @@ -174,7 +187,19 @@ public boolean checkSettingsCLI (final INotifier notifier, final Map box) + { + return Math.clamp (box.getSelectionModel ().getSelectedIndex (), 0, LAYER_OPTIONS.length - 1); + } + + + /** + * Get the index of the option which holds the given number of layers. + * + * @param layers The number of layers + * @return The index into LAYER_OPTIONS, the first one if the number is not an option + */ + private static int layersToIndex (final int layers) + { + for (int i = 0; i < LAYER_OPTIONS.length; i++) + if (LAYER_OPTIONS[i] == layers) + return i; + return 0; + } + + + /** + * Parse the value of the layers parameter. + * + * @param value The value + * @return The number of layers or -1 if the value is not one of the options + */ + private static int parseLayers (final String value) + { + try + { + final int layers = Integer.parseInt (value.trim ()); + return LAYER_OPTIONS[layersToIndex (layers)] == layers ? layers : -1; + } + catch (final NumberFormatException _) + { + return -1; + } + } } diff --git a/src/main/resources/Strings.properties b/src/main/resources/Strings.properties index 7fb356ca..b3fd4b52 100644 --- a/src/main/resources/Strings.properties +++ b/src/main/resources/Strings.properties @@ -516,6 +516,8 @@ IDS_QPAT_NOT_SAMPLE_BASED=Preset is not sample based.\n IDS_QPAT_LAYER_INACTIVE=Layer %1 is switched off and is therefore not converted.\n IDS_QPAT_LAYER_NOT_SAMPLE_BASED=Layer %1 is not sample based and is therefore not converted.\n IDS_QPAT_NOTIFY_LAYERS=The multi-sample is written as %1 layers with %2 sample maps.\n +IDS_QPAT_NOTIFY_FOUR_LAYERS=%1 layers need the four-layer layout of the MK2 generation (format version 15, four stored layers), which the first generation of the instruments with its two layers cannot play.\n +IDS_QPAT_CLI_LAYERS=The value of %1 must be 1, 2, 3 or 4.\n IDS_QPAT_NOTIFY_SHADOW_SAMPLES=%1 sample(s) which only a later layer plays are also named in the sample map of the first layer, since the device imports the samples of the first layer only.\n IDS_QPAT_NOTIFY_TOO_MANY_ENTRIES=Sample map %1 holds %2 entries, the device stores at most %3 and drops the rest.\n IDS_QPAT_OSC_SILENCED=Volume of oscillator %1 is set to -Infinity and might therefore not sound.\n @@ -924,7 +926,11 @@ IDS_QPAT_PATCH_INFO_TOOLTIP=Limited to what the device shows completely on its ' IDS_QPAT_NUMBER_PREFIX=Prefix file names with an import number IDS_QPAT_SHORT_FILE_NAMES=Shorten file names to the displayed preset name IDS_QPAT_NUMBER_PREFIX_START=First import number -IDS_QPAT_SECOND_LAYER=Use a second layer for more than 3 groups +IDS_QPAT_LAYERS=Maximum number of layers +IDS_QPAT_LAYERS_ONE=1 layer +IDS_QPAT_LAYERS_TWO=2 layers +IDS_QPAT_LAYERS_THREE=3 layers (MK2 only) +IDS_QPAT_LAYERS_FOUR=4 layers (MK2 only) IDS_SF2_NAMING=Naming of output files IDS_SF2_NAMING_ADD_FILE_NAME=Prefix with file name