diff --git a/CHANGELOG.md b/CHANGELOG.md index f03718f..d238e56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Changes -## 10.5.0 (unreleased) +## 10.2.0 -* Added support for Waldorf Quantum MkI/MkII, Iridium, Iridium Core sample format. +* Kontakt 1-4, MPC Keygroups, Soundfont 2, TAL Sampler, TX16Wx + * New: Added support for amplitude and filter velocity modulation. * Kontakt - Writing * New: Improved pitch envelope. * Kontakt 4.2-7 - Reading diff --git a/documentation/SupportedFeaturesSampleFormats.ods b/documentation/SupportedFeaturesSampleFormats.ods index 1ea083e..0e4b29e 100644 Binary files a/documentation/SupportedFeaturesSampleFormats.ods and b/documentation/SupportedFeaturesSampleFormats.ods differ diff --git a/pom.xml b/pom.xml index 2632f27..1e9c1f1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 de.mossgrabers convertwithmoss - 10.5.0 + 10.2.0 jar ConvertWithMoss diff --git a/src/main/java/de/mossgrabers/convertwithmoss/core/MathUtils.java b/src/main/java/de/mossgrabers/convertwithmoss/core/MathUtils.java index 4407fe1..60c584c 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/MathUtils.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/MathUtils.java @@ -23,34 +23,6 @@ private MathUtils () } - /** - * Limit the given value to the minimum/maximum range including minimum/maximum values. - * - * @param value The value to clamp - * @param minimum The minimum value - * @param maximum The maximum value - * @return The value clamped to the minimum/maximum range - */ - public static double clamp (final double value, final double minimum, final double maximum) - { - return Math.max (minimum, Math.min (value, maximum)); - } - - - /** - * Limit the given value to the minimum/maximum range including minimum/maximum values. - * - * @param value The value to clamp - * @param minimum The minimum value - * @param maximum The maximum value - * @return The value clamped to the minimum/maximum range - */ - public static int clamp (final int value, final int minimum, final int maximum) - { - return Math.max (minimum, Math.min (value, maximum)); - } - - /** * Converts a signed integer into a two complement short value. * @@ -122,7 +94,7 @@ public static double dBToDouble (final double dBValue) */ public static double normalizeFrequency (final double frequency, final double maxFrequency) { - return clamp (log2 (frequency) / log2 (maxFrequency), 0, 1); + return Math.clamp (log2 (frequency) / log2 (maxFrequency), 0, 1); } @@ -147,7 +119,7 @@ public static double denormalizeFrequency (final double normalizedFrequency, fin */ public static double normalizeCutoff (final double cutoffInHertz) { - return MathUtils.clamp ((log2 (cutoffInHertz / (2 * 440.0)) * 12.0 + 57) / 140.0, 0, 1); + return Math.clamp ((log2 (cutoffInHertz / (2 * 440.0)) * 12.0 + 57) / 140.0, 0, 1); } @@ -159,7 +131,7 @@ public static double normalizeCutoff (final double cutoffInHertz) */ public static double denormalizeCutoff (final double normalizedValue) { - return MathUtils.clamp (2.0 * 440.0 * Math.pow (2, (normalizedValue * 140.0 - 57.0) / 12.0), 32.7, 106300); + return Math.clamp (2.0 * 440.0 * Math.pow (2, (normalizedValue * 140.0 - 57.0) / 12.0), 32.7, 106300); } @@ -186,7 +158,7 @@ public static double normalize (final double value, final double maximum) */ public static double normalize (final double value, final double minimum, final double maximum) { - return clamp (value, minimum, maximum) / maximum; + return Math.clamp (value, minimum, maximum) / maximum; } @@ -200,7 +172,7 @@ public static double normalize (final double value, final double minimum, final */ public static double denormalize (final double value, final double minimum, final double maximum) { - return clamp (value * maximum, minimum, maximum); + return Math.clamp (value * maximum, minimum, maximum); } @@ -267,7 +239,7 @@ public static int normalizeTimeAsInt (final double value, final double maxValue) public static double normalizeTime (final double value, final double maxValue) { // value is negative if not set but 0 is fine then! - final double clamped = MathUtils.clamp (value, 0, maxValue); + final double clamped = Math.clamp (value, 0, maxValue); return Math.log (clamped + 1) / Math.log (maxValue + 1); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/core/creator/AbstractCreator.java b/src/main/java/de/mossgrabers/convertwithmoss/core/creator/AbstractCreator.java index 569b96d..4651b56 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/creator/AbstractCreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/creator/AbstractCreator.java @@ -33,7 +33,6 @@ import de.mossgrabers.convertwithmoss.core.AbstractCoreTask; import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.model.IGroup; import de.mossgrabers.convertwithmoss.core.model.IMetadata; import de.mossgrabers.convertwithmoss.core.model.ISampleData; @@ -650,7 +649,7 @@ private void rewriteFile (final IMetadata metadata, final ISampleZone zone, fina // Update information chunks if (this.isUpdateBroadcastAudioChunk ()) updateBroadcastAudioChunk (metadata, wavFile); - final int unityNote = MathUtils.clamp (zone.getKeyRoot (), 0, 127); + final int unityNote = Math.clamp (zone.getKeyRoot (), 0, 127); if (this.isUpdateInstrumentChunk ()) updateInstrumentChunk (zone, wavFile, unityNote); if (this.isUpdateSampleChunk ()) @@ -665,7 +664,7 @@ private void rewriteFile (final IMetadata metadata, final ISampleZone zone, fina /** * Trims the data of the wave file to the part from the zone start and zone end. The zone is * updated accordingly. - * + * * @param wavFile The WAV file to trim * @param zone The zone */ @@ -746,12 +745,12 @@ private static void updateInstrumentChunk (final ISampleZone zone, final WaveFil } instrumentChunk.setUnshiftedNote (unityNote); - instrumentChunk.setFineTune (MathUtils.clamp ((int) (zone.getTune () * 100), -50, 50)); - instrumentChunk.setGain (MathUtils.clamp ((int) zone.getGain (), -127, 127)); - instrumentChunk.setLowNote (MathUtils.clamp (zone.getKeyLow (), 0, 127)); - instrumentChunk.setHighNote (MathUtils.clamp (limitToDefault (zone.getKeyHigh (), 127), 0, 127)); - instrumentChunk.setLowVelocity (MathUtils.clamp (zone.getVelocityLow (), 0, 127)); - instrumentChunk.setHighVelocity (MathUtils.clamp (limitToDefault (zone.getVelocityHigh (), 127), 0, 127)); + instrumentChunk.setFineTune (Math.clamp ((int) (zone.getTune () * 100), -50, 50)); + instrumentChunk.setGain (Math.clamp ((int) zone.getGain (), -127, 127)); + instrumentChunk.setLowNote (Math.clamp (zone.getKeyLow (), 0, 127)); + instrumentChunk.setHighNote (Math.clamp (limitToDefault (zone.getKeyHigh (), 127), 0, 127)); + instrumentChunk.setLowVelocity (Math.clamp (zone.getVelocityLow (), 0, 127)); + instrumentChunk.setHighVelocity (Math.clamp (limitToDefault (zone.getVelocityHigh (), 127), 0, 127)); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/core/detector/AbstractDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/core/detector/AbstractDetectorTask.java index f1d4c4c..64675e7 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/detector/AbstractDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/detector/AbstractDetectorTask.java @@ -30,7 +30,6 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.model.IFileBasedSampleData; import de.mossgrabers.convertwithmoss.core.model.IGroup; import de.mossgrabers.convertwithmoss.core.model.IMetadata; @@ -348,7 +347,7 @@ protected String loadTextFile (final File file) throws IOException */ protected static double denormalizeValue (final double value, final double minimum, final double maximum) { - return minimum + MathUtils.clamp (value, 0, 1) * (maximum - minimum); + return minimum + Math.clamp (value, 0, 1) * (maximum - minimum); } @@ -558,7 +557,7 @@ protected File findSampleFile (final File folder, final File previousFolder, fin } // ... and search recursively... - final File found = findSampleFileRecursively (startDirectory, sampleFile.getName ()); + final File found = this.findSampleFileRecursively (startDirectory, sampleFile.getName ()); // Returning the original file triggers the expected error... if (found == null) return sampleFile; @@ -600,7 +599,7 @@ private File findSampleFileRecursively (final File folder, final String fileName if (children != null) for (final File subFolder: children) { - sampleFile = findSampleFileRecursively (subFolder, fileName); + sampleFile = this.findSampleFileRecursively (subFolder, fileName); if (sampleFile != null) return sampleFile; } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultFilter.java b/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultFilter.java index 31a9058..ecc594b 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultFilter.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultFilter.java @@ -22,8 +22,8 @@ public class DefaultFilter implements IFilter protected double cutoff; protected double resonance; protected int envelopeDepth; - protected IEnvelopeModulator cutoffEnvelopeModulator = new DefaultEnvelopeModulator (1); - protected IModulator cutoffVelocityModulator = new DefaultModulator (1); + protected IEnvelopeModulator cutoffEnvelopeModulator = new DefaultEnvelopeModulator (0); + protected IModulator cutoffVelocityModulator = new DefaultModulator (0); /** diff --git a/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultModulator.java b/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultModulator.java index f0bf13c..a761d35 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultModulator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultModulator.java @@ -4,7 +4,6 @@ package de.mossgrabers.convertwithmoss.core.model.implementation; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.model.IModulator; @@ -25,7 +24,7 @@ public class DefaultModulator implements IModulator */ public DefaultModulator (final double depth) { - this.depth = MathUtils.clamp (depth, -1, 1); + this.depth = Math.clamp (depth, -1, 1); } @@ -41,7 +40,7 @@ public double getDepth () @Override public void setDepth (final double depth) { - this.depth = MathUtils.clamp (depth, -1.0, 1.0); + this.depth = Math.clamp (depth, -1.0, 1.0); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultSampleLoop.java b/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultSampleLoop.java index 953f3a4..2afb29c 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultSampleLoop.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultSampleLoop.java @@ -4,7 +4,6 @@ package de.mossgrabers.convertwithmoss.core.model.implementation; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.model.ISampleLoop; import de.mossgrabers.convertwithmoss.core.model.enumeration.LoopType; @@ -101,7 +100,7 @@ public int getCrossfadeInSamples () @Override public void setCrossfade (final double crossfade) { - this.crossfade = MathUtils.clamp (crossfade, 0.0, 1.0); + this.crossfade = Math.clamp (crossfade, 0.0, 1.0); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultSampleZone.java b/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultSampleZone.java index 89c22d5..8e9547b 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultSampleZone.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/model/implementation/DefaultSampleZone.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Optional; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.model.IEnvelopeModulator; import de.mossgrabers.convertwithmoss.core.model.IFilter; import de.mossgrabers.convertwithmoss.core.model.IModulator; @@ -340,7 +339,7 @@ public void setVelocityCrossfadeHigh (final int crossfadeHigh) @Override public void setGain (final double gain) { - this.gain = MathUtils.clamp (gain, -12.0, 12.0); + this.gain = Math.clamp (gain, -12.0, 12.0); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/file/AudioFileUtils.java b/src/main/java/de/mossgrabers/convertwithmoss/file/AudioFileUtils.java index 8b99bd3..4f1ddba 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/file/AudioFileUtils.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/file/AudioFileUtils.java @@ -276,12 +276,10 @@ public static WaveFile convertToWav (final ISampleData sampleData, final Destina // AudioSystem handles 32bit float values incorrect. We need our own implementation. if (is32BitFloat) - { try (AudioInputStream convertedAudioInputStream = convertAudioStreamFrom32BitFloatTo16BitPCM (audioInputStream, audioFormat, newAudioFormat)) { return doConvertToWav (convertedAudioInputStream, newAudioFormat); } - } return doConvertToWav (audioInputStream, newAudioFormat); } @@ -303,7 +301,7 @@ private static AudioInputStream convertAudioStreamFrom32BitFloatTo16BitPCM (fina for (int i = 0; i < sourceData.length; i += 4) { - float floatValue = inputBuffer.getFloat (i); + final float floatValue = inputBuffer.getFloat (i); // Convert float to 16-bit PCM outputBuffer.putShort ((short) (floatValue * Short.MAX_VALUE)); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/file/sf2/Sf2Modulator.java b/src/main/java/de/mossgrabers/convertwithmoss/file/sf2/Sf2Modulator.java index 8e43137..eb4f25f 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/file/sf2/Sf2Modulator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/file/sf2/Sf2Modulator.java @@ -15,6 +15,8 @@ */ public class Sf2Modulator { + /** The ID for a Velocity modulator. */ + public static final Integer MODULATOR_VELOCITY = Integer.valueOf (2); /** The ID for a Pitch Bend modulator. */ public static final Integer MODULATOR_PITCH_BEND = Integer.valueOf (14); @@ -30,7 +32,7 @@ public class Sf2Modulator * The controller source to be used is the velocity value which is sent from the MIDI * note-on command which generated the given sound. */ - MODULATOR_NAMES.put (Integer.valueOf (2), "Note-On Velocity"); + MODULATOR_NAMES.put (MODULATOR_VELOCITY, "Note-On Velocity"); /** * The controller source to be used is the key number value which was sent from the MIDI * note-on command which generated the given sound. diff --git a/src/main/java/de/mossgrabers/convertwithmoss/file/wav/FormatChunk.java b/src/main/java/de/mossgrabers/convertwithmoss/file/wav/FormatChunk.java index 667f438..e097be1 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/file/wav/FormatChunk.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/file/wav/FormatChunk.java @@ -302,7 +302,7 @@ public void setSignificantBitsPerSample (final int bitsPerSample) */ public int calculateLength (final byte [] data) { - return data.length / calculateBytesPerSample (); + return data.length / this.calculateBytesPerSample (); } @@ -314,14 +314,14 @@ public int calculateLength (final byte [] data) */ public int calculateDataSize (final int lengthInSamples) { - return lengthInSamples * calculateBytesPerSample (); + return lengthInSamples * this.calculateBytesPerSample (); } /** * Calculate the number of bytes which are used for one sample depending on the significant bite * per sample and the number of channels. - * + * * @return The number of bytes */ public int calculateBytesPerSample () diff --git a/src/main/java/de/mossgrabers/convertwithmoss/file/wav/SampleChunk.java b/src/main/java/de/mossgrabers/convertwithmoss/file/wav/SampleChunk.java index cc54169..184a720 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/file/wav/SampleChunk.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/file/wav/SampleChunk.java @@ -150,7 +150,7 @@ public int getMIDIUnityNoteRaw () public int getMIDIUnityNote () { final int unityNote = this.getFourBytesAsInt (0x0C); - return getMIDIPitchFractionAsCents () < 0 ? unityNote + 1 : unityNote; + return this.getMIDIPitchFractionAsCents () < 0 ? unityNote + 1 : unityNote; } @@ -192,7 +192,7 @@ public long getMIDIPitchFraction () */ public int getMIDIPitchFractionAsCents () { - long midiPitchFraction = this.getMIDIPitchFraction (); + final long midiPitchFraction = this.getMIDIPitchFraction (); final int value = (int) Math.round (midiPitchFraction * 50.0 / 0x80000000L); return value > 50 ? value - 100 : value; } @@ -201,7 +201,7 @@ public int getMIDIPitchFractionAsCents () /** * Sets the unity note and the pitch fraction. If the cents are negative the unity note and * fraction are adapted accordingly. - * + * * @param unityNote The unity note to set * @param cent The pitch adjustment in cents */ diff --git a/src/main/java/de/mossgrabers/convertwithmoss/file/wav/WaveFile.java b/src/main/java/de/mossgrabers/convertwithmoss/file/wav/WaveFile.java index 39b1118..cfb399f 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/file/wav/WaveFile.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/file/wav/WaveFile.java @@ -132,7 +132,7 @@ public void setBroadcastAudioExtensionChunk (final BroadcastAudioExtensionChunk { this.broadcastAudioExtensionChunk = broadcastAudioExtensionChunk; this.chunkStack.clear (); - fillChunkStack (); + this.fillChunkStack (); } @@ -156,7 +156,7 @@ public void setInstrumentChunk (final InstrumentChunk instrumentChunk) { this.instrumentChunk = instrumentChunk; this.chunkStack.clear (); - fillChunkStack (); + this.fillChunkStack (); } @@ -180,7 +180,7 @@ public void setSampleChunk (final SampleChunk sampleChunk) { this.sampleChunk = sampleChunk; this.chunkStack.clear (); - fillChunkStack (); + this.fillChunkStack (); } @@ -204,7 +204,7 @@ public void setDataChunk (final DataChunk dataChunk) { this.dataChunk = dataChunk; this.chunkStack.clear (); - fillChunkStack (); + this.fillChunkStack (); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/ableton/AbletonCreator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/ableton/AbletonCreator.java index 3f42a13..a4f8798 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/ableton/AbletonCreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/ableton/AbletonCreator.java @@ -18,7 +18,6 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.creator.AbstractCreator; import de.mossgrabers.convertwithmoss.core.model.IAudioMetadata; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; @@ -335,7 +334,7 @@ else if (cents < -50) cents += 100; } - zoneContent = zoneContent.replace ("%ROOT_KEY%", Integer.toString (MathUtils.clamp (limitToDefault (zone.getKeyRoot (), keyLow) - semitones, 0, 127))); + zoneContent = zoneContent.replace ("%ROOT_KEY%", Integer.toString (Math.clamp (limitToDefault (zone.getKeyRoot (), keyLow) - semitones, 0, 127))); zoneContent = zoneContent.replace ("%DETUNE%", Integer.toString (cents)); zoneContent = zoneContent.replace ("%TUNE_SCALE%", Integer.toString ((int) (zone.getKeyTracking () * 100))); zoneContent = zoneContent.replace ("%PANORAMA%", formatDouble (zone.getPanorama ())); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/ableton/AbletonDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/ableton/AbletonDetectorTask.java index 111dbca..a703bb0 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/ableton/AbletonDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/ableton/AbletonDetectorTask.java @@ -27,7 +27,6 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.detector.AbstractDetectorTask; import de.mossgrabers.convertwithmoss.core.detector.DefaultMultisampleSource; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; @@ -322,7 +321,7 @@ private static void readZone (final ISampleZone zone, final Element multiSampleP zone.setKeyRoot (AbletonDetectorTask.getIntegerValueAttribute (multiSamplePartElement, AbletonTag.TAG_ROOT_KEY, 60)); zone.setTune (AbletonDetectorTask.getIntegerValueAttribute (multiSamplePartElement, AbletonTag.TAG_DETUNE, 0) / 100.0); - zone.setKeyTracking (MathUtils.clamp (AbletonDetectorTask.getIntegerValueAttribute (multiSamplePartElement, AbletonTag.TAG_TUNE_SCALE, 0) / 100.0, 0, 1)); + zone.setKeyTracking (Math.clamp (AbletonDetectorTask.getIntegerValueAttribute (multiSamplePartElement, AbletonTag.TAG_TUNE_SCALE, 0) / 100.0, 0, 1)); zone.setPanorama (AbletonDetectorTask.getDoubleValueAttribute (multiSamplePartElement, AbletonTag.TAG_PANORAMA, 0)); final double volumeVal = AbletonDetectorTask.getDoubleValueAttribute (multiSamplePartElement, AbletonTag.TAG_VOLUME, 1); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupCreator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupCreator.java index ef8e8a6..09a462c 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupCreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupCreator.java @@ -226,7 +226,7 @@ private static Element createLayerElement (final Document document, final int la XMLUtils.addTextElement (document, layerElement, MPCKeygroupTag.LAYER_ACTIVE, MPCKeygroupTag.TRUE); XMLUtils.addTextElement (document, layerElement, MPCKeygroupTag.LAYER_VOLUME, Double.toString (convertGain (zone.getGain ()))); - final double pan = (MathUtils.clamp (zone.getPanorama (), -1.0d, 1.0d) + 1.0d) / 2.0d; + final double pan = (Math.clamp (zone.getPanorama (), -1.0d, 1.0d) + 1.0d) / 2.0d; XMLUtils.addTextElement (document, layerElement, MPCKeygroupTag.LAYER_PAN, String.format (Locale.US, "%.6f", Double.valueOf (pan))); final double tuneCent = zone.getTune (); @@ -334,6 +334,9 @@ private static Optional getKeygroup (final Map> instrumentElement.setAttribute ("number", Integer.toString (calcInstrumentNumber (keygroupsMap))); instrumentsElement.appendChild (instrumentElement); + ///////////////////////////////////////////////////////////// + // Filter + final Optional optFilter = zone.getFilter (); if (optFilter.isPresent ()) { @@ -359,12 +362,26 @@ private static Optional getKeygroup (final Map> setEnvelopeCurveAttribute (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_FILTER_DECAY_CURVE, filterEnvelope.getDecaySlope ()); setEnvelopeCurveAttribute (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_FILTER_RELEASE_CURVE, filterEnvelope.getReleaseSlope ()); } + + final double filterCutoffVelocityAmount = filter.getCutoffVelocityModulator ().getDepth (); + if (filterCutoffVelocityAmount > 0) + XMLUtils.addTextElement (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_VELOCITY_TO_FILTER_AMOUNT, formatDouble (filterCutoffVelocityAmount, 2)); } + ///////////////////////////////////////////////////////////// + // Range + XMLUtils.addTextElement (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_LOW_NOTE, Integer.toString (keyLow)); XMLUtils.addTextElement (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_HIGH_NOTE, Integer.toString (keyHigh)); XMLUtils.addTextElement (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_IGNORE_BASE_NOTE, zone.getKeyTracking () == 0 ? "True" : "False"); + ///////////////////////////////////////////////////////////// + // Amplitude + + final double ampVelocityAmount = zone.getAmplitudeVelocityModulator ().getDepth (); + if (ampVelocityAmount > 0) + XMLUtils.addTextElement (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_VELOCITY_TO_AMP_AMOUNT, formatDouble (ampVelocityAmount, 2)); + final IEnvelope amplitudeEnvelope = zone.getAmplitudeEnvelopeModulator ().getSource (); setEnvelopeAttribute (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_VOLUME_ATTACK, amplitudeEnvelope.getAttackTime (), MPCKeygroupConstants.MIN_ENV_TIME_SECONDS, MPCKeygroupConstants.MAX_ENV_TIME_SECONDS, MPCKeygroupConstants.DEFAULT_ATTACK_TIME, true); setEnvelopeAttribute (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_VOLUME_HOLD, amplitudeEnvelope.getHoldTime (), MPCKeygroupConstants.MIN_ENV_TIME_SECONDS, MPCKeygroupConstants.MAX_ENV_TIME_SECONDS, MPCKeygroupConstants.DEFAULT_HOLD_TIME, true); @@ -375,6 +392,9 @@ private static Optional getKeygroup (final Map> setEnvelopeCurveAttribute (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_VOLUME_DECAY_CURVE, amplitudeEnvelope.getDecaySlope ()); setEnvelopeCurveAttribute (document, instrumentElement, MPCKeygroupTag.INSTRUMENT_VOLUME_RELEASE_CURVE, amplitudeEnvelope.getReleaseSlope ()); + ///////////////////////////////////////////////////////////// + // Pitch + final IEnvelopeModulator pitchModulator = zone.getPitchModulator (); final double pitchDepth = pitchModulator.getDepth (); // Only positive modulation values are supported with MPC @@ -458,7 +478,7 @@ private static double convertGain (final double volumeDB) */ private static double normalizeLogarithmicEnvTimeValue (final double value, final double minimum, final double maximum) { - return Math.log (MathUtils.clamp (value, minimum, maximum) / minimum) / Math.log (maximum / minimum); + return Math.log (Math.clamp (value, minimum, maximum) / minimum) / Math.log (maximum / minimum); } @@ -478,7 +498,7 @@ private static void setEnvelopeAttribute (final Document document, final Element private static void setEnvelopeCurveAttribute (final Document document, final Element element, final String curveTag, final double slopeValue) { - final double value = MathUtils.clamp ((slopeValue + 1.0) / 2.0, 0, 1); + final double value = Math.clamp ((slopeValue + 1.0) / 2.0, 0, 1); XMLUtils.addTextElement (document, element, curveTag, String.format (Locale.US, "%.6f", Double.valueOf (value))); } } \ No newline at end of file diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupDetectorTask.java index c11a312..677c63e 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupDetectorTask.java @@ -22,13 +22,12 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.detector.AbstractDetectorTask; import de.mossgrabers.convertwithmoss.core.detector.DefaultMultisampleSource; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; +import de.mossgrabers.convertwithmoss.core.model.IEnvelopeModulator; import de.mossgrabers.convertwithmoss.core.model.IFilter; import de.mossgrabers.convertwithmoss.core.model.IGroup; -import de.mossgrabers.convertwithmoss.core.model.IEnvelopeModulator; import de.mossgrabers.convertwithmoss.core.model.ISampleData; import de.mossgrabers.convertwithmoss.core.model.ISampleZone; import de.mossgrabers.convertwithmoss.core.model.enumeration.PlayLogic; @@ -268,30 +267,40 @@ private List parseGroups (final File basePath, final int numKeygroups, f final int velStart = XMLUtils.getChildElementIntegerContent (layerElement, MPCKeygroupTag.LAYER_VEL_START, 0); final int velEnd = XMLUtils.getChildElementIntegerContent (layerElement, MPCKeygroupTag.LAYER_VEL_END, 0); - final DefaultSampleZone sampleMetadata = this.parseSampleData (layerElement, basePath, keyLow, keyHigh, velStart, velEnd, zonePlay, ignoreBaseNote, triggerType); - if (sampleMetadata == null) + final DefaultSampleZone zone = this.parseSampleData (layerElement, basePath, keyLow, keyHigh, velStart, velEnd, zonePlay, ignoreBaseNote, triggerType); + if (zone == null) continue; - samples.add (sampleMetadata); + samples.add (zone); - final IEnvelopeModulator amplitudeModulator = sampleMetadata.getAmplitudeEnvelopeModulator (); + ///////////////////////////////////////////////////////////// + // Amplitude + + final IEnvelopeModulator amplitudeModulator = zone.getAmplitudeEnvelopeModulator (); amplitudeModulator.setDepth (1.0); amplitudeModulator.getSource ().set (volumeEnvelope); + final double ampVelocityAmount = XMLUtils.getChildElementDoubleContent (instrumentElement, MPCKeygroupTag.INSTRUMENT_VELOCITY_TO_AMP_AMOUNT, 0); + if (ampVelocityAmount > 0) + zone.getAmplitudeVelocityModulator ().setDepth (ampVelocityAmount); + + ///////////////////////////////////////////////////////////// + // Pitch + final double pitchEnvAmount = XMLUtils.getChildElementDoubleContent (instrumentElement, MPCKeygroupTag.INSTRUMENT_PITCH_ENV_AMOUNT, 0.5); if (pitchEnvAmount != 0.5) { - final IEnvelopeModulator pitchModulator = sampleMetadata.getPitchModulator (); + final IEnvelopeModulator pitchModulator = zone.getPitchModulator (); pitchModulator.setDepth ((pitchEnvAmount - 0.5) * 2.0); pitchModulator.getSource ().set (pitchEnvelope); } // No loop if it is a one-shot if (!isOneShot) - parseLoop (layerElement, sampleMetadata); + parseLoop (layerElement, zone); - sampleMetadata.setFilter (filter); + zone.setFilter (filter); - this.readMissingData (isDrum, isOneShot, sampleMetadata); + this.readMissingData (isDrum, isOneShot, zone); } } @@ -323,6 +332,11 @@ private static IFilter parseFilter (final Element instrumentElement) cutoffModulator.setDepth (filterAmount); cutoffModulator.getSource ().set (parseEnvelope (instrumentElement, MPCKeygroupTag.INSTRUMENT_FILTER_ATTACK, MPCKeygroupTag.INSTRUMENT_FILTER_HOLD, MPCKeygroupTag.INSTRUMENT_FILTER_DECAY, MPCKeygroupTag.INSTRUMENT_FILTER_SUSTAIN, MPCKeygroupTag.INSTRUMENT_FILTER_RELEASE, MPCKeygroupTag.INSTRUMENT_FILTER_ATTACK_CURVE, MPCKeygroupTag.INSTRUMENT_FILTER_DECAY_CURVE, MPCKeygroupTag.INSTRUMENT_FILTER_RELEASE_CURVE)); } + + final double filterCutoffVelocityAmount = XMLUtils.getChildElementDoubleContent (instrumentElement, MPCKeygroupTag.INSTRUMENT_VELOCITY_TO_FILTER_AMOUNT, 0); + if (filterCutoffVelocityAmount > 0) + filter.getCutoffVelocityModulator ().setDepth (filterCutoffVelocityAmount); + return filter; } @@ -390,7 +404,7 @@ private DefaultSampleZone parseSampleData (final Element layerElement, final Fil final String panStr = XMLUtils.getChildElementContent (layerElement, MPCKeygroupTag.LAYER_PAN); if (panStr != null && !panStr.isBlank ()) - sampleMetadata.setPanorama (MathUtils.clamp (Double.parseDouble (panStr) * 2.0d - 1.0d, -1.0d, 1.0d)); + sampleMetadata.setPanorama (Math.clamp (Double.parseDouble (panStr) * 2.0d - 1.0d, -1.0d, 1.0d)); final String pitchStr = XMLUtils.getChildElementContent (layerElement, MPCKeygroupTag.LAYER_PITCH); if (pitchStr != null && !pitchStr.isBlank ()) @@ -576,9 +590,9 @@ private static IEnvelope parseEnvelope (final Element element, final String atta envelope.setSustainLevel (getEnvelopeAttribute (element, sustainTag, 0, 1, 1, false)); - envelope.setAttackSlope (MathUtils.clamp (XMLUtils.getChildElementDoubleContent (element, attackCurveTag, 0.5) * 2.0 - 1.0, -1.0, 1.0)); - envelope.setDecaySlope (MathUtils.clamp (XMLUtils.getChildElementDoubleContent (element, decayCurveTag, 0.5) * 2.0 - 1.0, -1.0, 1.0)); - envelope.setReleaseSlope (MathUtils.clamp (XMLUtils.getChildElementDoubleContent (element, releaseCurveTag, 0.5) * 2.0 - 1.0, -1.0, 1.0)); + envelope.setAttackSlope (Math.clamp (XMLUtils.getChildElementDoubleContent (element, attackCurveTag, 0.5) * 2.0 - 1.0, -1.0, 1.0)); + envelope.setDecaySlope (Math.clamp (XMLUtils.getChildElementDoubleContent (element, decayCurveTag, 0.5) * 2.0 - 1.0, -1.0, 1.0)); + envelope.setReleaseSlope (Math.clamp (XMLUtils.getChildElementDoubleContent (element, releaseCurveTag, 0.5) * 2.0 - 1.0, -1.0, 1.0)); return envelope; } @@ -595,6 +609,6 @@ private static double getEnvelopeAttribute (final Element element, final String private static double denormalizeLogarithmicEnvTimeValue (final double value, final double minimum, final double maximum) { - return minimum * Math.exp (MathUtils.clamp (value, 0, 1) * Math.log (maximum / minimum)); + return minimum * Math.exp (Math.clamp (value, 0, 1) * Math.log (maximum / minimum)); } } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupTag.java b/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupTag.java index da26586..939af1a 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupTag.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/akai/MPCKeygroupTag.java @@ -15,206 +15,211 @@ public class MPCKeygroupTag // Elements /** The root element. */ - public static final String ROOT = "MPCVObject"; + public static final String ROOT = "MPCVObject"; /** The program element. */ - public static final String ROOT_PROGRAM = "Program"; + public static final String ROOT_PROGRAM = "Program"; /** The version element. */ - public static final String ROOT_VERSION = "Version"; + public static final String ROOT_VERSION = "Version"; /** The file version element. */ - public static final String VERSION_FILE_VERSION = "File_Version"; + public static final String VERSION_FILE_VERSION = "File_Version"; /** The program name element. */ - public static final String PROGRAM_NAME = "ProgramName"; + public static final String PROGRAM_NAME = "ProgramName"; /** The program instrument element. */ - public static final String PROGRAM_INSTRUMENTS = "Instruments"; + public static final String PROGRAM_INSTRUMENTS = "Instruments"; /** The program pad note map element. */ - public static final String PROGRAM_PAD_NOTE_MAP = "PadNoteMap"; + public static final String PROGRAM_PAD_NOTE_MAP = "PadNoteMap"; /** The program number of keygroups element. */ - public static final String PROGRAM_NUM_KEYGROUPS = "KeygroupNumKeygroups"; + public static final String PROGRAM_NUM_KEYGROUPS = "KeygroupNumKeygroups"; /** The program pads setup element. */ - public static final String PROGRAM_PADS = "ProgramPads-"; + public static final String PROGRAM_PADS = "ProgramPads-"; /** The program keygroup pitch bend range element. */ - public static final String PROGRAM_PITCHBEND_RANGE = "KeygroupPitchBendRange"; + public static final String PROGRAM_PITCHBEND_RANGE = "KeygroupPitchBendRange"; /** The program keygroup wheel to LFO element. */ - public static final String PROGRAM_WHEEL_TO_LFO = "KeygroupWheelToLfo"; + public static final String PROGRAM_WHEEL_TO_LFO = "KeygroupWheelToLfo"; /** The instruments instrument element. */ - public static final String INSTRUMENTS_INSTRUMENT = "Instrument"; + public static final String INSTRUMENTS_INSTRUMENT = "Instrument"; /** The low note element of the instrument element. */ - public static final String INSTRUMENT_LOW_NOTE = "LowNote"; + public static final String INSTRUMENT_LOW_NOTE = "LowNote"; /** The high note element of the instrument element. */ - public static final String INSTRUMENT_HIGH_NOTE = "HighNote"; + public static final String INSTRUMENT_HIGH_NOTE = "HighNote"; /** The ignore base note element of the instrument element. */ - public static final String INSTRUMENT_IGNORE_BASE_NOTE = "IgnoreBaseNote"; + public static final String INSTRUMENT_IGNORE_BASE_NOTE = "IgnoreBaseNote"; /** The zone play element of the instrument element. */ - public static final String INSTRUMENT_ZONE_PLAY = "ZonePlay"; + public static final String INSTRUMENT_ZONE_PLAY = "ZonePlay"; /** The trigger mode element of the instrument element. */ - public static final String INSTRUMENT_TRIGGER_MODE = "TriggerMode"; + public static final String INSTRUMENT_TRIGGER_MODE = "TriggerMode"; /** The one-shot element of the instrument element. */ - public static final String INSTRUMENT_ONE_SHOT = "OneShot"; + public static final String INSTRUMENT_ONE_SHOT = "OneShot"; /** The layers element of the instrument element. */ - public static final String INSTRUMENT_LAYERS = "Layers"; + public static final String INSTRUMENT_LAYERS = "Layers"; /** The filter type element of the instrument element. */ - public static final String INSTRUMENT_FILTER_TYPE = "FilterType"; + public static final String INSTRUMENT_FILTER_TYPE = "FilterType"; /** The filter cutoff element of the instrument element. */ - public static final String INSTRUMENT_FILTER_CUTOFF = "Cutoff"; + public static final String INSTRUMENT_FILTER_CUTOFF = "Cutoff"; /** The filter resonance element of the instrument element. */ - public static final String INSTRUMENT_FILTER_RESONANCE = "Resonance"; + public static final String INSTRUMENT_FILTER_RESONANCE = "Resonance"; /** The filter envelope amount element of the instrument element. */ - public static final String INSTRUMENT_FILTER_ENV_AMOUNT = "FilterEnvAmt"; + public static final String INSTRUMENT_FILTER_ENV_AMOUNT = "FilterEnvAmt"; + /** The cutoff velocity amount of the instrument element. */ + public static final String INSTRUMENT_VELOCITY_TO_FILTER_AMOUNT = "VelocityToFilter"; /** The filter attack element of the instrument element. */ - public static final String INSTRUMENT_FILTER_ATTACK = "FilterAttack"; + public static final String INSTRUMENT_FILTER_ATTACK = "FilterAttack"; /** The filter hold element of the instrument element. */ - public static final String INSTRUMENT_FILTER_HOLD = "FilterHold"; + public static final String INSTRUMENT_FILTER_HOLD = "FilterHold"; /** The filter decay element of the instrument element. */ - public static final String INSTRUMENT_FILTER_DECAY = "FilterDecay"; + public static final String INSTRUMENT_FILTER_DECAY = "FilterDecay"; /** The filter sustain element of the instrument element. */ - public static final String INSTRUMENT_FILTER_SUSTAIN = "FilterSustain"; + public static final String INSTRUMENT_FILTER_SUSTAIN = "FilterSustain"; /** The filter release element of the instrument element. */ - public static final String INSTRUMENT_FILTER_RELEASE = "FilterRelease"; + public static final String INSTRUMENT_FILTER_RELEASE = "FilterRelease"; /** The filter attack curve element of the instrument element. */ - public static final String INSTRUMENT_FILTER_ATTACK_CURVE = "FilterAttackCurve"; + public static final String INSTRUMENT_FILTER_ATTACK_CURVE = "FilterAttackCurve"; /** The filter decay curve element of the instrument element. */ - public static final String INSTRUMENT_FILTER_DECAY_CURVE = "FilterDecayCurve"; + public static final String INSTRUMENT_FILTER_DECAY_CURVE = "FilterDecayCurve"; /** The filter release curve element of the instrument element. */ - public static final String INSTRUMENT_FILTER_RELEASE_CURVE = "FilterReleaseCurve"; + public static final String INSTRUMENT_FILTER_RELEASE_CURVE = "FilterReleaseCurve"; + + /** The amplitude velocity amount element of the instrument element. */ + public static final String INSTRUMENT_VELOCITY_TO_AMP_AMOUNT = "VelocitySensitivity"; /** The volume attack element of the instrument element. */ - public static final String INSTRUMENT_VOLUME_ATTACK = "VolumeAttack"; + public static final String INSTRUMENT_VOLUME_ATTACK = "VolumeAttack"; /** The volume hold element of the instrument element. */ - public static final String INSTRUMENT_VOLUME_HOLD = "VolumeHold"; + public static final String INSTRUMENT_VOLUME_HOLD = "VolumeHold"; /** The volume decay element of the instrument element. */ - public static final String INSTRUMENT_VOLUME_DECAY = "VolumeDecay"; + public static final String INSTRUMENT_VOLUME_DECAY = "VolumeDecay"; /** The volume sustain element of the instrument element. */ - public static final String INSTRUMENT_VOLUME_SUSTAIN = "VolumeSustain"; + public static final String INSTRUMENT_VOLUME_SUSTAIN = "VolumeSustain"; /** The volume release element of the instrument element. */ - public static final String INSTRUMENT_VOLUME_RELEASE = "VolumeRelease"; + public static final String INSTRUMENT_VOLUME_RELEASE = "VolumeRelease"; /** The volume attack curve element of the instrument element. */ - public static final String INSTRUMENT_VOLUME_ATTACK_CURVE = "VolumeAttackCurve"; + public static final String INSTRUMENT_VOLUME_ATTACK_CURVE = "VolumeAttackCurve"; /** The volume decay curve element of the instrument element. */ - public static final String INSTRUMENT_VOLUME_DECAY_CURVE = "VolumeDecayCurve"; + public static final String INSTRUMENT_VOLUME_DECAY_CURVE = "VolumeDecayCurve"; /** The volume release curve element of the instrument element. */ - public static final String INSTRUMENT_VOLUME_RELEASE_CURVE = "VolumeReleaseCurve"; + public static final String INSTRUMENT_VOLUME_RELEASE_CURVE = "VolumeReleaseCurve"; /** The pitch attack element of the instrument element. */ - public static final String INSTRUMENT_PITCH_ATTACK = "PitchAttack"; + public static final String INSTRUMENT_PITCH_ATTACK = "PitchAttack"; /** The pitch hold element of the instrument element. */ - public static final String INSTRUMENT_PITCH_HOLD = "PitchHold"; + public static final String INSTRUMENT_PITCH_HOLD = "PitchHold"; /** The pitch decay element of the instrument element. */ - public static final String INSTRUMENT_PITCH_DECAY = "PitchDecay"; + public static final String INSTRUMENT_PITCH_DECAY = "PitchDecay"; /** The pitch sustain element of the instrument element. */ - public static final String INSTRUMENT_PITCH_SUSTAIN = "PitchSustain"; + public static final String INSTRUMENT_PITCH_SUSTAIN = "PitchSustain"; /** The pitch release element of the instrument element. */ - public static final String INSTRUMENT_PITCH_RELEASE = "PitchRelease"; + public static final String INSTRUMENT_PITCH_RELEASE = "PitchRelease"; /** The pitch attack curve element of the instrument element. */ - public static final String INSTRUMENT_PITCH_ATTACK_CURVE = "PitchAttackCurve"; + public static final String INSTRUMENT_PITCH_ATTACK_CURVE = "PitchAttackCurve"; /** The pitch decay curve element of the instrument element. */ - public static final String INSTRUMENT_PITCH_DECAY_CURVE = "PitchDecayCurve"; + public static final String INSTRUMENT_PITCH_DECAY_CURVE = "PitchDecayCurve"; /** The pitch release curve element of the instrument element. */ - public static final String INSTRUMENT_PITCH_RELEASE_CURVE = "PitchReleaseCurve"; + public static final String INSTRUMENT_PITCH_RELEASE_CURVE = "PitchReleaseCurve"; /** The pitch envelope amount element of the instrument element. */ - public static final String INSTRUMENT_PITCH_ENV_AMOUNT = "PitchEnvAmount"; + public static final String INSTRUMENT_PITCH_ENV_AMOUNT = "PitchEnvAmount"; /** The layer element of the layers element. */ - public static final String LAYERS_LAYER = "Layer"; + public static final String LAYERS_LAYER = "Layer"; /** The sample name element of the layer element. */ - public static final String LAYER_SAMPLE_NAME = "SampleName"; + public static final String LAYER_SAMPLE_NAME = "SampleName"; /** The active element of the layer element. */ - public static final String LAYER_ACTIVE = "Active"; + public static final String LAYER_ACTIVE = "Active"; /** The volume element of the layer element. */ - public static final String LAYER_VOLUME = "Volume"; + public static final String LAYER_VOLUME = "Volume"; /** The panorama element of the layer element. */ - public static final String LAYER_PAN = "Pan"; + public static final String LAYER_PAN = "Pan"; /** The pitch element of the layer element. */ - public static final String LAYER_PITCH = "Pitch"; + public static final String LAYER_PITCH = "Pitch"; /** The coarse tune element of the layer element. */ - public static final String LAYER_COARSE_TUNE = "TuneCoarse"; + public static final String LAYER_COARSE_TUNE = "TuneCoarse"; /** The fine tune element of the layer element. */ - public static final String LAYER_FINE_TUNE = "TuneFine"; + public static final String LAYER_FINE_TUNE = "TuneFine"; /** The root note element of the layer element. */ - public static final String LAYER_ROOT_NOTE = "RootNote"; + public static final String LAYER_ROOT_NOTE = "RootNote"; /** The key track element of the layer element. */ - public static final String LAYER_KEY_TRACK = "KeyTrack"; + public static final String LAYER_KEY_TRACK = "KeyTrack"; /** The velocity start element of the instrument element. */ - public static final String LAYER_VEL_START = "VelStart"; + public static final String LAYER_VEL_START = "VelStart"; /** The velocity end element of the instrument element. */ - public static final String LAYER_VEL_END = "VelEnd"; + public static final String LAYER_VEL_END = "VelEnd"; /** The sample start element of the layer element. */ - public static final String LAYER_SAMPLE_START = "SampleStart"; + public static final String LAYER_SAMPLE_START = "SampleStart"; /** The sample end element of the layer element. */ - public static final String LAYER_SAMPLE_END = "SampleEnd"; + public static final String LAYER_SAMPLE_END = "SampleEnd"; /** The loop start element of the layer element. */ - public static final String LAYER_LOOP_START = "LoopStart"; + public static final String LAYER_LOOP_START = "LoopStart"; /** The loop end element of the layer element. */ - public static final String LAYER_LOOP_END = "LoopEnd"; + public static final String LAYER_LOOP_END = "LoopEnd"; /** The loop crossfade element of the layer element. */ - public static final String LAYER_LOOP_CROSSFADE = "LoopCrossfadeLength"; + public static final String LAYER_LOOP_CROSSFADE = "LoopCrossfadeLength"; /** The loop tune element of the layer element. */ - public static final String LAYER_LOOP_TUNE = "LoopTune"; + public static final String LAYER_LOOP_TUNE = "LoopTune"; /** The pitch randomization element of the layer element. */ - public static final String LAYER_PITCH_RANDOM = "PitchRandom"; + public static final String LAYER_PITCH_RANDOM = "PitchRandom"; /** The volume randomization element of the layer element. */ - public static final String LAYER_VOLUME_RANDOM = "VolumeRandom"; + public static final String LAYER_VOLUME_RANDOM = "VolumeRandom"; /** The panorama randomization element of the layer element. */ - public static final String LAYER_PAN_RANDOM = "PanRandom"; + public static final String LAYER_PAN_RANDOM = "PanRandom"; /** The offset randomization element of the layer element. */ - public static final String LAYER_OFFSET_RANDOM = "OffsetRandom"; + public static final String LAYER_OFFSET_RANDOM = "OffsetRandom"; /** The sample file element of the layer element. */ - public static final String LAYER_SAMPLE_FILE = "SampleFile"; + public static final String LAYER_SAMPLE_FILE = "SampleFile"; /** The slice index element of the layer element. */ - public static final String LAYER_SLICE_INDEX = "SliceIndex"; + public static final String LAYER_SLICE_INDEX = "SliceIndex"; /** The direction element of the layer element. */ - public static final String LAYER_DIRECTION = "Direction"; + public static final String LAYER_DIRECTION = "Direction"; /** The offset element of the layer element. */ - public static final String LAYER_OFFSET = "Offset"; + public static final String LAYER_OFFSET = "Offset"; /** The slice start element of the layer element. */ - public static final String LAYER_SLICE_START = "SliceStart"; + public static final String LAYER_SLICE_START = "SliceStart"; /** The slice end element of the layer element. */ - public static final String LAYER_SLICE_END = "SliceEnd"; + public static final String LAYER_SLICE_END = "SliceEnd"; /** The slice loop element of the layer element. */ - public static final String LAYER_SLICE_LOOP = "SliceLoop"; + public static final String LAYER_SLICE_LOOP = "SliceLoop"; /** The slice loop start element of the layer element. */ - public static final String LAYER_SLICE_LOOP_START = "SliceLoopStart"; + public static final String LAYER_SLICE_LOOP_START = "SliceLoopStart"; /** The slice loop crossfade element of the layer element. */ - public static final String LAYER_SLICE_LOOP_CROSSFADE = "SliceLoopCrossFadeLength"; + public static final String LAYER_SLICE_LOOP_CROSSFADE = "SliceLoopCrossFadeLength"; /** The slice tail position element of the layer element. */ - public static final String LAYER_SLICE_TAIL_POSITION = "SliceTailPosition"; + public static final String LAYER_SLICE_TAIL_POSITION = "SliceTailPosition"; /** The slice tail length element of the layer element. */ - public static final String LAYER_SLICE_TAIL_LENGTH = "SliceTailLength"; + public static final String LAYER_SLICE_TAIL_LENGTH = "SliceTailLength"; /** The pad note element of the pad note map element. */ - public static final String PAD_NOTE_MAP_PAD_NOTE = "PadNote"; + public static final String PAD_NOTE_MAP_PAD_NOTE = "PadNote"; /** The pad note element of the pad note map element. */ - public static final String PAD_NOTE_NOTE = "Note"; + public static final String PAD_NOTE_NOTE = "Note"; /////////////////////////////////////////////////////// // Attributes /** The type attribute of the program element. */ - public static final String PROGRAM_TYPE = "type"; + public static final String PROGRAM_TYPE = "type"; /** The number attribute of the instrument element. */ - public static final String INSTRUMENT_NUMBER = "number"; + public static final String INSTRUMENT_NUMBER = "number"; /** The number attribute of the pad note element. */ - public static final String PAD_NOTE_NUMBER = "number"; + public static final String PAD_NOTE_NUMBER = "number"; /** The program type keygroup. */ - public static final String TYPE_KEYGROUP = "Keygroup"; + public static final String TYPE_KEYGROUP = "Keygroup"; /** The program type drum. */ - public static final String TYPE_DRUM = "Drum"; + public static final String TYPE_DRUM = "Drum"; /** The true value. */ - public static final String TRUE = "True"; + public static final String TRUE = "True"; /** diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/disting/DistingExCreator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/disting/DistingExCreator.java index 4beb0ac..a7cc947 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/disting/DistingExCreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/disting/DistingExCreator.java @@ -205,7 +205,7 @@ private static void storeMultisample (final IMultisampleSource multisampleSource parameters[13] = (int) (tune * 100.0); // Gain in the range of -40..24 dB - parameters[14] = MathUtils.clamp ((int) Math.round (zone.getGain ()), -40, 24); + parameters[14] = Math.clamp ((int) Math.round (zone.getGain ()), -40, 24); final Optional modulator = multisampleSource.getGlobalAmplitudeModulator (); if (modulator.isPresent ()) @@ -214,9 +214,9 @@ private static void storeMultisample (final IMultisampleSource multisampleSource if (envelopeModulator.hashCode () > 0) { final IEnvelope envelope = envelopeModulator.getSource (); - parameters[7] = MathUtils.clamp ((int) Math.round (Math.log (envelope.getAttackTime () / 0.001) / 0.0757), 0, 127); - parameters[8] = MathUtils.clamp ((int) Math.round (Math.log (envelope.getDecayTime () / 0.02) / 0.0521), 0, 127); - parameters[10] = MathUtils.clamp ((int) Math.round (Math.log (envelope.getReleaseTime () / 0.01) / 0.0630), 0, 127); + parameters[7] = Math.clamp ((int) Math.round (Math.log (envelope.getAttackTime () / 0.001) / 0.0757), 0, 127); + parameters[8] = Math.clamp ((int) Math.round (Math.log (envelope.getDecayTime () / 0.02) / 0.0521), 0, 127); + parameters[10] = Math.clamp ((int) Math.round (Math.log (envelope.getReleaseTime () / 0.01) / 0.0630), 0, 127); } } } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/disting/DistingExDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/disting/DistingExDetectorTask.java index 14aa462..adb4ca7 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/disting/DistingExDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/disting/DistingExDetectorTask.java @@ -222,7 +222,7 @@ private static void calculateKeyAndVelocityRanges (final List groupsList { final IGroup group = groupsList.get (i); final int velocityLow = 1 + i * velocitySteps; - int velocityHigh = MathUtils.clamp ((i + 1) * velocitySteps, 1, 127); + int velocityHigh = Math.clamp ((i + 1) * velocitySteps, 1, 127); // Ensure that the last step reaches till the highest velocity if (i == size - 1) velocityHigh = 127; diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/exs/EXS24Creator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/exs/EXS24Creator.java index ce7eb37..002cc88 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/exs/EXS24Creator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/exs/EXS24Creator.java @@ -206,7 +206,7 @@ private static void storeMultisample (final IMultisampleSource multisampleSource exsParameters.put (EXS24Parameters.PITCH_BEND_DOWN, Math.abs (zone.getBendDown () / 100)); final double velocityDepth = zone.getAmplitudeVelocityModulator ().getDepth (); - final int velocityModulation = (int) Math.round (MathUtils.clamp ((1 - velocityDepth) * -60.0, -60, 0)); + final int velocityModulation = (int) Math.round (Math.clamp ((1 - velocityDepth) * -60.0, -60, 0)); exsParameters.put (EXS24Parameters.ENV1_VEL_SENS, velocityModulation); createEnvelope (exsParameters, 1, zone.getAmplitudeEnvelopeModulator ()); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/exs/EXS24DetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/exs/EXS24DetectorTask.java index a053f4a..7ab250b 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/exs/EXS24DetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/exs/EXS24DetectorTask.java @@ -213,7 +213,7 @@ private Optional createMultisample (final File file, final M if (exs24Zone.pitch && (exs24Zone.coarseTuning != 0 || exs24Zone.fineTuning != 0)) zone.setTune (exs24Zone.coarseTuning + exs24Zone.fineTuning / 100.0); - zone.setPanorama (MathUtils.clamp (exs24Zone.pan, -50, 50) / 50.0); + zone.setPanorama (Math.clamp (exs24Zone.pan, -50, 50) / 50.0); if (exs24Zone.loopOn) { @@ -286,7 +286,7 @@ private Pair createAndCheckSampleZone (final File parentFile, } final int height = this.levelsOfDirectorySearch.getSelectionModel ().getSelectedItem ().intValue (); - final File sampleFile = findSampleFile (parentFile, previousFolder, exs24Sample.fileName, height); + final File sampleFile = this.findSampleFile (parentFile, previousFolder, exs24Sample.fileName, height); if (!sampleFile.exists ()) { this.notifier.logError ("IDS_NOTIFY_ERR_SAMPLE_DOES_NOT_EXIST", sampleFile.getAbsolutePath ()); @@ -316,7 +316,7 @@ private static void applyGlobalParameters (final IMultisampleSource multisampleS final IEnvelope globalAmplitudeEnvelope = createEnvelope (parameters, 1); final Integer env1Velocity = parameters.get (EXS24Parameters.ENV1_VEL_SENS); - final double velocityModulation = env1Velocity == null ? 1 : 1 - MathUtils.clamp (env1Velocity.intValue () / -60.0, 0, 1); + final double velocityModulation = env1Velocity == null ? 1 : 1 - Math.clamp (env1Velocity.intValue () / -60.0, 0, 1); for (final IGroup group: multisampleSource.getGroups ()) for (final ISampleZone zone: group.getSampleZones ()) @@ -408,7 +408,7 @@ private static void applyFilterParameters (final IMultisampleSource multisampleS final int resonance = filterResonance == null ? 0 : filterResonance.intValue (); final double cutoff = MathUtils.denormalize (frequency / 1000.0, 0, IFilter.MAX_FREQUENCY); - final IFilter filter = new DefaultFilter (filterType, poles, cutoff, MathUtils.clamp (resonance / 1000.0, 0, 1)); + final IFilter filter = new DefaultFilter (filterType, poles, cutoff, Math.clamp (resonance / 1000.0, 0, 1)); final IEnvelopeModulator cutoffModulator = filter.getCutoffEnvelopeModulator (); cutoffModulator.setDepth (1.0); cutoffModulator.setSource (globalFilterEnvelope); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KMPFile.java b/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KMPFile.java index e0105ee..e0a8989 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KMPFile.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KMPFile.java @@ -15,7 +15,6 @@ import java.util.List; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.creator.AbstractCreator; import de.mossgrabers.convertwithmoss.core.model.IGroup; import de.mossgrabers.convertwithmoss.core.model.ISampleZone; @@ -349,7 +348,7 @@ private void writeParameterChunk1 (final DataOutputStream out) throws IOExceptio out.write (keyHigh); out.writeByte ((byte) Math.round (zone.getTune () * 100.0)); - out.writeByte ((byte) MathUtils.clamp (Math.round (MathUtils.clamp (zone.getGain (), -12, 12) / 12.0 * 100.0), -99, 99)); + out.writeByte ((byte) Math.clamp (Math.round (Math.clamp (zone.getGain (), -12, 12) / 12.0 * 100.0), -99, 99)); // Panorama - unused in KMP itself, 64 is center out.write (64); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/music1010/Music1010DetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/music1010/Music1010DetectorTask.java index c36cf44..0ffbe73 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/music1010/Music1010DetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/music1010/Music1010DetectorTask.java @@ -442,7 +442,7 @@ private static void parseEffects (final Element paramsElement, final DefaultMult // represents. Therefore, we assume 40dB maximum and a linear range (could also // be logarithmic). final int resonance = XMLUtils.getIntegerAttribute (paramsElement, Music1010Tag.ATTR_FILTER_RESONANCE, 0); - multisampleSource.setGlobalFilter (new DefaultFilter (type, 4, cutoff, MathUtils.clamp (resonance / 1000.0, 0, 1.0))); + multisampleSource.setGlobalFilter (new DefaultFilter (type, 4, cutoff, Math.clamp (resonance / 1000.0, 0, 1.0))); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/nki/AbstractNKIMetadataFileHandler.java b/src/main/java/de/mossgrabers/convertwithmoss/format/nki/AbstractNKIMetadataFileHandler.java index 731251f..6dd851b 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/nki/AbstractNKIMetadataFileHandler.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/nki/AbstractNKIMetadataFileHandler.java @@ -350,7 +350,7 @@ private static String addModulators (final ISampleZone sampleMetadata, final Str final IFilter filter = filterOpt.isPresent () ? filterOpt.get () : new DefaultFilter (FilterType.LOW_PASS, 2, IFilter.MAX_FREQUENCY, 0); groupContent = groupContent.replace ("%FILTER_TYPE%", createFilterType (filter)); - final double legacyCutoff = MathUtils.normalizeFrequency (MathUtils.clamp (filter.getCutoff (), 43.6, 21800), 21800.0); + final double legacyCutoff = MathUtils.normalizeFrequency (Math.clamp (filter.getCutoff (), 43.6, 21800), 21800.0); groupContent = groupContent.replace ("%FILTER_CUTOFF%", formatDouble (legacyCutoff)); groupContent = groupContent.replace ("%FILTER_RESONANCE%", formatDouble (filter.getResonance ())); @@ -363,6 +363,10 @@ private static String addModulators (final ISampleZone sampleMetadata, final Str groupContent = groupContent.replace ("%FILTER_CUTOFF_ENVELOPE_HOLD%", formatDouble (limitToDefault (filterCutoffEnvelope.getHoldTime (), 0) * 1000.0d)); groupContent = groupContent.replace ("%FILTER_CUTOFF_ENVELOPE_RELEASE%", formatDouble (limitToDefault (filterCutoffEnvelope.getReleaseTime (), 1) * 1000.0d)); groupContent = groupContent.replace ("%FILTER_CUTOFF_ENVELOPE_SUSTAIN%", formatDouble (limitToDefault (filterCutoffEnvelope.getSustainLevel (), 1))); + + final double cutoffVelocityDepth = filter.getCutoffVelocityModulator ().getDepth (); + groupContent = groupContent.replace ("%FILTER_CUTOFF_VELOCITY_MOD%", formatDouble (limitToDefault (cutoffVelocityDepth, 0))); + return groupContent; } @@ -693,7 +697,7 @@ private void readMetadata (final Map programParameters, final Ma final double groupPan = AbstractNKIMetadataFileHandler.getDouble (groupParameters, this.tags.groupPanParam ()); final double progPan = AbstractNKIMetadataFileHandler.getDouble (programParameters, this.tags.progPanParam ()); final double totalPan = this.normalizePanning (zonePan) + this.normalizePanning (groupPan) + this.normalizePanning (progPan); - zone.setPanorama (MathUtils.clamp (totalPan, -1.0d, 1.0d)); + zone.setPanorama (Math.clamp (totalPan, -1.0d, 1.0d)); if (ampVelocityMod >= 0) zone.getAmplitudeVelocityModulator ().setDepth (ampVelocityMod); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/nki/type/kontakt5/Program.java b/src/main/java/de/mossgrabers/convertwithmoss/format/nki/type/kontakt5/Program.java index 5bfbadd..6cae68b 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/nki/type/kontakt5/Program.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/nki/type/kontakt5/Program.java @@ -296,7 +296,7 @@ public void fillInto (final DefaultMultisampleSource source) throws IOException final float volume = this.instrumentVolume * kontaktGroup.getVolume () * kontaktZone.getZoneVolume (); zone.setGain (MathUtils.valueToDb (volume)); - zone.setPanorama (MathUtils.clamp (this.instrumentPan + kontaktGroup.getPan () + kontaktZone.getZonePan (), -1, 1)); + zone.setPanorama (Math.clamp (this.instrumentPan + kontaktGroup.getPan () + kontaktZone.getZonePan (), -1, 1)); zone.setTune (calculateTune (kontaktZone.getZoneTune (), kontaktGroup.getTune (), this.instrumentTune)); zone.setKeyTracking (kontaktGroup.isKeyTracking () ? 1 : 0); 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 ba5e772..010bab3 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Creator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2Creator.java @@ -14,7 +14,6 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.creator.AbstractCreator; import de.mossgrabers.convertwithmoss.core.creator.DestinationAudioFormat; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; @@ -291,6 +290,10 @@ else if (sampleType == Sf2SampleDescriptor.RIGHT) // Gain instrumentZone.addGenerator (Generator.INITIAL_ATTENUATION, (int) (-sampleZone.getGain () * 10.0)); + final double ampDepth = sampleZone.getAmplitudeVelocityModulator ().getDepth (); + if (ampDepth != 0) + instrumentZone.addModulator (Sf2Modulator.MODULATOR_VELOCITY.intValue (), Generator.INITIAL_ATTENUATION, (int) Math.round (ampDepth * 960), 0, 0); + // Volume envelope final IEnvelope amplitudeEnvelope = sampleZone.getAmplitudeEnvelopeModulator ().getSource (); @@ -331,8 +334,8 @@ else if (sampleType == Sf2SampleDescriptor.RIGHT) // always a relation of two frequencies, 1200 cents are one octave: // cents = 1200 * log2 (f1 / f2), f2 = 8.176 => f1 = f2 * 2^(cents / 1200) final double initialCutoff = Math.log (frequency / 8.176) * 1200.0 / Math.log (2); - instrumentZone.addGenerator (Generator.INITIAL_FILTER_CUTOFF, (int) MathUtils.clamp (initialCutoff, 1500, 13500)); - instrumentZone.addSignedGenerator (Generator.INITIAL_FILTER_RESONANCE, (int) (MathUtils.clamp (resonance, 0, 960) * 100.0)); + instrumentZone.addGenerator (Generator.INITIAL_FILTER_CUTOFF, (int) Math.clamp (initialCutoff, 1500, 13500)); + instrumentZone.addSignedGenerator (Generator.INITIAL_FILTER_RESONANCE, (int) (Math.clamp (resonance, 0, 960) * 100.0)); final IEnvelopeModulator cutoffModulator = filter.getCutoffEnvelopeModulator (); final double cutoffModDepth = cutoffModulator.getDepth (); @@ -347,6 +350,10 @@ else if (sampleType == Sf2SampleDescriptor.RIGHT) setEnvelopeTime (instrumentZone, Generator.MOD_ENV_RELEASE, filterEnvelope.getReleaseTime ()); setEnvelopeLevel (instrumentZone, Generator.MOD_ENV_SUSTAIN, filterEnvelope.getSustainLevel ()); } + + final double cutoffDepth = filter.getCutoffVelocityModulator ().getDepth (); + if (cutoffDepth != 0) + instrumentZone.addModulator (Sf2Modulator.MODULATOR_VELOCITY.intValue (), Generator.INITIAL_FILTER_CUTOFF, (int) Math.round (cutoffDepth * -2400), 0, 0); } } @@ -417,7 +424,7 @@ private static Sf2SampleDescriptor createSf2SampleDescriptor (final int sampleTy sampleDescriptor.setSampleType (sampleType); sampleDescriptor.setSampleRate (formatChunk.getSampleRate ()); - sampleDescriptor.setOriginalPitch (MathUtils.clamp (sampleZone.getKeyRoot (), 0, 127)); + sampleDescriptor.setOriginalPitch (Math.clamp (sampleZone.getKeyRoot (), 0, 127)); sampleDescriptor.setPitchCorrection ((int) (sampleZone.getTune () * 100)); String name = sampleZone.getName (); @@ -460,7 +467,7 @@ private static int convertEnvelopeVolume (final double value) // Attenuation is in centi-bel (dB / 10), so 0 is maximum volume, about 1000 is off // This is likely not correct but since there is also no documentation what the percentage // volume values mean in dB it is the best we can do... - return (int) MathUtils.clamp ((1.0 - value) * 1000.0, 0, 1000); + return (int) Math.clamp ((1.0 - value) * 1000.0, 0, 1000); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2DetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2DetectorTask.java index 80f3799..3818878 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2DetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sf2/Sf2DetectorTask.java @@ -14,15 +14,14 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.creator.AbstractCreator; import de.mossgrabers.convertwithmoss.core.detector.AbstractDetectorTask; import de.mossgrabers.convertwithmoss.core.detector.DefaultMultisampleSource; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; +import de.mossgrabers.convertwithmoss.core.model.IEnvelopeModulator; import de.mossgrabers.convertwithmoss.core.model.IFilter; import de.mossgrabers.convertwithmoss.core.model.IGroup; import de.mossgrabers.convertwithmoss.core.model.IMetadata; -import de.mossgrabers.convertwithmoss.core.model.IEnvelopeModulator; import de.mossgrabers.convertwithmoss.core.model.ISampleZone; import de.mossgrabers.convertwithmoss.core.model.enumeration.FilterType; import de.mossgrabers.convertwithmoss.core.model.implementation.DefaultFilter; @@ -162,18 +161,37 @@ private List parseSF2File (final File sourceFile, final Sf2F } - private static void parseModulators (final ISampleZone sampleZone, final Sf2PresetZone zone, final Sf2InstrumentZone instrZone) + private static void parseModulators (final ISampleZone zone, final Sf2PresetZone sf2Zone, final Sf2InstrumentZone instrZone) { - List modulators = instrZone.getModulators (Sf2Modulator.MODULATOR_PITCH_BEND); - if (modulators.isEmpty ()) - modulators = zone.getModulators (Sf2Modulator.MODULATOR_PITCH_BEND); - for (final Sf2Modulator sf2Modulator: modulators) + for (final Sf2Modulator sf2Modulator: getModulators (sf2Zone, instrZone, Sf2Modulator.MODULATOR_PITCH_BEND)) if (sf2Modulator.getDestinationGenerator () == Generator.FINE_TUNE) { final int amount = sf2Modulator.getModulationAmount (); - sampleZone.setBendUp (amount); - sampleZone.setBendDown (-amount); + zone.setBendUp (amount); + zone.setBendDown (-amount); + } + + for (final Sf2Modulator sf2Modulator: getModulators (sf2Zone, instrZone, Sf2Modulator.MODULATOR_VELOCITY)) + { + final int destinationGenerator = sf2Modulator.getDestinationGenerator (); + if (destinationGenerator == Generator.INITIAL_ATTENUATION) + { + final int amount = sf2Modulator.getModulationAmount (); + zone.getAmplitudeVelocityModulator ().setDepth (Math.clamp (amount / 960, 0, 1)); } + else if (destinationGenerator == Generator.INITIAL_FILTER_CUTOFF) + { + final int amount = sf2Modulator.getModulationAmount (); + zone.getAmplitudeVelocityModulator ().setDepth (Math.clamp (amount / -2400, 0, 1)); + } + } + } + + + private static List getModulators (final Sf2PresetZone zone, final Sf2InstrumentZone instrZone, final Integer modulatorID) + { + final List modulators = instrZone.getModulators (modulatorID); + return modulators.isEmpty () ? zone.getModulators (modulatorID) : modulators; } @@ -271,7 +289,7 @@ private List combineLinkedSamples (final List leftSamp // Store the matching right side sample with the left side one leftSampleData.setRightSample (sample); updateFilename (leftSampleZone, rightSampleZone); - leftSampleZone.setPanorama (MathUtils.clamp (leftSampleZone.getPanorama () + rightSampleZone.getPanorama (), -1.0, 1.0)); + leftSampleZone.setPanorama (Math.clamp (leftSampleZone.getPanorama () + rightSampleZone.getPanorama (), -1.0, 1.0)); resultSamples.add (leftSampleZone); rightSampleZones.remove (i); found = true; @@ -326,7 +344,7 @@ private List combinePanoramaSamples (final List panLef final Sf2SampleData leftSampleData = (Sf2SampleData) panLeftSampleZone.getSampleData (); updateFilename (panLeftSampleZone, panRightSampleZone); leftSampleData.setRightSample (((Sf2SampleData) panRightSampleZone.getSampleData ()).getSample ()); - panLeftSampleZone.setPanorama (MathUtils.clamp (panLeftSampleZone.getPanorama () + panRightSampleZone.getPanorama (), -1.0, 1.0)); + panLeftSampleZone.setPanorama (Math.clamp (panLeftSampleZone.getPanorama () + panRightSampleZone.getPanorama (), -1.0, 1.0)); resultSamples.add (panLeftSampleZone); panRightSamples.remove (i); found = true; @@ -408,10 +426,10 @@ private static ISampleZone createSampleZone (final Sf2SampleDescriptor sample, f zone.setKeyRoot (pitch); final int fineTune = generators.getSignedValue (Generator.FINE_TUNE).intValue (); final int pitchCorrection = sample.getPitchCorrection (); - final double tune = Math.min (1, Math.max (-1, (pitchCorrection + (double) fineTune) / 100)); + final double tune = Math.clamp ((pitchCorrection + (double) fineTune) / 100, -1, 1); zone.setTune (tune); final int scaleTuning = generators.getSignedValue (Generator.SCALE_TUNE).intValue (); - zone.setKeyTracking (Math.min (100, Math.max (0, scaleTuning)) / 100.0); + zone.setKeyTracking (Math.clamp (scaleTuning / 100.0, 0, 100)); // Set the key range final Pair keyRangeValue = generators.getRangeValue (Generator.KEY_RANGE); @@ -538,7 +556,7 @@ private static double convertEnvelopeVolume (final Integer value) // This is likely not correct but since there is also no documentation what the percentage // volume values mean in dB it is the best we can do... - return Math.max (0, Math.min (1.0, 1.0 - v / 1000.0)); + return Math.clamp (1.0 - v / 1000.0, 0, 1); } 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 4565c04..6be2d5e 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzCreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzCreator.java @@ -19,7 +19,6 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.creator.AbstractCreator; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; import de.mossgrabers.convertwithmoss.core.model.IEnvelopeModulator; @@ -481,7 +480,7 @@ private static void createFilter (final StringBuilder buffer, final ISampleZone final IFilter filter = optFilter.get (); final String type = FILTER_TYPE_MAP.get (filter.getType ()); - addAttribute (buffer, SfzOpcode.FILTER_TYPE, type + "_" + MathUtils.clamp (filter.getPoles (), 1, 4) + "p", false); + addAttribute (buffer, SfzOpcode.FILTER_TYPE, type + "_" + Math.clamp (filter.getPoles (), 1, 4) + "p", false); addAttribute (buffer, SfzOpcode.CUTOFF, formatDouble (filter.getCutoff (), 2), false); final double velFilterDepth = filter.getCutoffVelocityModulator ().getDepth (); @@ -539,7 +538,7 @@ private static void addSlopeAttribute (final StringBuilder sb, final String opco return; if (sb.length () > 0) sb.append (' '); - sb.append (opcode).append ('=').append (MathUtils.clamp (value, -10.0, 10.0)); + sb.append (opcode).append ('=').append (Math.clamp (value, -10.0, 10.0)); } @@ -549,6 +548,6 @@ private static void addEnvelopeAttribute (final StringBuilder sb, final String o return; if (sb.length () > 0) sb.append (' '); - sb.append (opcode).append ('=').append (MathUtils.clamp (value, 0.0, 100.0)); + sb.append (opcode).append ('=').append (Math.clamp (value, 0.0, 100.0)); } } \ No newline at end of file diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzDetectorTask.java index e13cf1e..c7f98fc 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sfz/SfzDetectorTask.java @@ -21,7 +21,6 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.NoteParser; import de.mossgrabers.convertwithmoss.core.detector.AbstractDetectorTask; import de.mossgrabers.convertwithmoss.core.detector.DefaultMultisampleSource; @@ -410,10 +409,10 @@ private void parseRegion (final ISampleZone sampleMetadata) double tune = this.getDoubleValue (SfzOpcode.TUNE, 0); if (tune == 0) tune = this.getDoubleValue (SfzOpcode.PITCH, 0); - sampleMetadata.setTune (MathUtils.clamp (tune, -3600, 3600) / 100.0); + sampleMetadata.setTune (Math.clamp (tune, -3600, 3600) / 100.0); final double pitchKeytrack = this.getDoubleValue (SfzOpcode.PITCH_KEYTRACK, 100); - sampleMetadata.setKeyTracking (MathUtils.clamp (pitchKeytrack, 0, 100) / 100.0); + sampleMetadata.setKeyTracking (Math.clamp (pitchKeytrack, 0, 100) / 100.0); sampleMetadata.setBendUp ((int) (this.getIntegerValue (SfzOpcode.BEND_UP, 0) / 100.0)); sampleMetadata.setBendDown ((int) (this.getIntegerValue (SfzOpcode.BEND_DOWN, 0) / 100.0)); @@ -580,7 +579,7 @@ private void parseVolume (final ISampleZone sampleZone) { sampleZone.setGain (this.getDoubleValue (SfzOpcode.VOLUME, 0)); final double panorama = this.getDoubleValue (SfzOpcode.PANORAMA, 0); - sampleZone.setPanorama (MathUtils.clamp (panorama, -100, 100) / 100.0); + sampleZone.setPanorama (Math.clamp (panorama, -100, 100) / 100.0); // Amplitude envelope diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/sxt/SxtDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/sxt/SxtDetectorTask.java index 776851c..ef99955 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/sxt/SxtDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sxt/SxtDetectorTask.java @@ -332,7 +332,7 @@ private File parseReference (final InputStream in, final File parentFile, final // Find the sample file final int height = this.levelsOfDirectorySearch.getSelectionModel ().getSelectedItem ().intValue (); - return findSampleFile (parentFile, previousFolder, sampleFileName, height); + return this.findSampleFile (parentFile, previousFolder, sampleFileName, height); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/sxt/SxtZone.java b/src/main/java/de/mossgrabers/convertwithmoss/format/sxt/SxtZone.java index b48e4b8..d6efde9 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/sxt/SxtZone.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/sxt/SxtZone.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Optional; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.creator.AbstractCreator; import de.mossgrabers.convertwithmoss.core.model.IAudioMetadata; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; @@ -505,7 +504,9 @@ public void fillInto (final ISampleZone zone) if (this.alternateMode > 0) zone.setPlayLogic (PlayLogic.ROUND_ROBIN); - // Pitch Modulation envelope + ////////////////////////////////////////////////////////// + // Pitch + if (this.modEnvToPitch > 0) { final IEnvelopeModulator pitchModulator = zone.getPitchModulator (); @@ -522,7 +523,9 @@ public void fillInto (final ISampleZone zone) modEnvelope.setReleaseTime (envelopeTimeCentsToSeconds (this.modEnvRelease)); } - // Set filter + ////////////////////////////////////////////////////////// + // Filter + if (this.filterIsOn > 0) { final double freqHz = 440 * Math.pow (2, (this.filterFreq - 6900) / 1200); @@ -566,6 +569,7 @@ public void fillInto (final ISampleZone zone) } final IFilter filter = new DefaultFilter (type, poles, freqHz, this.filterResonance / 1000.0); zone.setFilter (filter); + if (this.modEnvToFilterFreq != 0) { final IEnvelopeModulator cutoffModulator = filter.getCutoffEnvelopeModulator (); @@ -581,9 +585,17 @@ public void fillInto (final ISampleZone zone) modEnvelope.setSustainLevel (this.modEnvSustain / 1000.0); modEnvelope.setReleaseTime (envelopeTimeCentsToSeconds (this.modEnvRelease)); } + + if (this.velocityToFilterFreq != 0) + filter.getCutoffVelocityModulator ().setDepth (this.velocityToFilterFreq / 1000.0); } - // Set amplitude envelope + ////////////////////////////////////////////////////////// + // Amplitude + + if (this.velocityToAmpGain != 0) + zone.getAmplitudeVelocityModulator ().setDepth (this.velocityToAmpGain / 1000.0); + final IEnvelopeModulator amplitudeModulator = zone.getAmplitudeEnvelopeModulator (); final IEnvelope ampEnvelope = amplitudeModulator.getSource (); if (this.ampEnvDelayIsOff == 0) @@ -612,11 +624,11 @@ public void fillFrom (final ISampleZone zone) throws IOException { this.keyRangeStart = AbstractCreator.limitToDefault (zone.getKeyLow (), 0); this.keyRangeEnd = AbstractCreator.limitToDefault (zone.getKeyHigh (), 127); - this.velocityRangeStart = MathUtils.clamp (zone.getVelocityLow (), 1, 127); - this.velocityRangeEnd = MathUtils.clamp (AbstractCreator.limitToDefault (zone.getVelocityHigh (), 127), 1, 127); + this.velocityRangeStart = Math.clamp (zone.getVelocityLow (), 1, 127); + this.velocityRangeEnd = Math.clamp (AbstractCreator.limitToDefault (zone.getVelocityHigh (), 127), 1, 127); this.velocityFadeIn = zone.getVelocityCrossfadeLow (); final int velocityCrossfadeHigh = zone.getVelocityCrossfadeHigh (); - this.velocityFadeOut = velocityCrossfadeHigh == 0 ? 0x80 : MathUtils.clamp (127 - velocityCrossfadeHigh, 1, 127); + this.velocityFadeOut = velocityCrossfadeHigh == 0 ? 0x80 : Math.clamp (127 - velocityCrossfadeHigh, 1, 127); this.rootKey = AbstractCreator.limitToDefault (zone.getKeyRoot (), this.keyRangeStart); this.sampleStart = zone.getStart (); this.sampleEnd = zone.getStop (); @@ -632,7 +644,9 @@ public void fillFrom (final ISampleZone zone) throws IOException this.pitchWheelRange = zone.getBendUp (); this.pitchWheelRange = zone.getBendDown (); + ////////////////////////////////////////////////////////// // Loop + final List loops = zone.getLoops (); if (loops.isEmpty ()) this.playMode = 0; @@ -649,7 +663,9 @@ else if (zone.isReversed ()) if (zone.getPlayLogic () == PlayLogic.ROUND_ROBIN) this.alternateMode = 1; - // Pitch Modulation envelope + ////////////////////////////////////////////////////////// + // Pitch + final IEnvelopeModulator pitchModulator = zone.getPitchModulator (); final double depth = pitchModulator.getDepth (); if (depth > 0) @@ -680,7 +696,9 @@ else if (zone.isReversed ()) this.modEnvRelease = envelopeTimeSecondsToCents (modEnvelope.getReleaseTime ()); } - // Set filter + ////////////////////////////////////////////////////////// + // Filter + final Optional optFilter = zone.getFilter (); if (optFilter.isPresent ()) { @@ -746,9 +764,15 @@ else if (poles == 2) this.modEnvSustain = sustain < 0 ? 1000 : (int) (sustain * 1000); this.modEnvRelease = envelopeTimeSecondsToCents (modEnvelope.getReleaseTime ()); } + + final double cutoffVelocityAmount = filter.getCutoffVelocityModulator ().getDepth (); + if (cutoffVelocityAmount != 0) + this.velocityToFilterFreq = (int) Math.round (cutoffVelocityAmount * 1000.0); } - // Set amplitude envelope + ////////////////////////////////////////////////////////// + // Amplitude + final IEnvelopeModulator amplitudeModulator = zone.getAmplitudeEnvelopeModulator (); final IEnvelope ampEnvelope = amplitudeModulator.getSource (); final double delay = ampEnvelope.getDelayTime (); @@ -774,6 +798,10 @@ else if (poles == 2) this.ampEnvSustain = sustain < 0 ? 1000 : (int) (sustain * 1000); this.ampEnvRelease = envelopeTimeSecondsToCents (ampEnvelope.getReleaseTime ()); + final double ampVelocityAmount = zone.getAmplitudeVelocityModulator ().getDepth (); + if (ampVelocityAmount != 0) + this.velocityToAmpGain = (int) Math.round (ampVelocityAmount * 1000.0); + // Set gain and panorama final double dBValue = zone.getGain (); final double gainRatio = Math.pow (10, dBValue / 20); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerConstants.java b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerConstants.java index 6159f97..8f2f1fa 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerConstants.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerConstants.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Optional; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.model.IFilter; import de.mossgrabers.convertwithmoss.core.model.IGroup; import de.mossgrabers.convertwithmoss.core.model.ISampleZone; @@ -119,7 +118,7 @@ public static double getFilterValue (final IFilter filter) */ public static Optional getFilterType (final double value) { - final int filterIndex = MathUtils.clamp ((int) Math.round (value / INDEX_OFFSET), 0, 12); + final int filterIndex = Math.clamp ((int) Math.round (value / INDEX_OFFSET), 0, 12); switch (filterIndex) { case 0, 1, 2, 3, 4, 5, 6: diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerCreator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerCreator.java index 158a0c2..1e73237 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerCreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerCreator.java @@ -159,11 +159,9 @@ private Optional createMetadata (final String folderName, final IMultisa programElement.setAttribute (TALSamplerTag.PROGRAM_NAME, multisampleSource.getName ()); programElement.setAttribute (TALSamplerTag.PROGRAM_NUM_VOICES, "1.0"); - final List groups = this.optimizeGroups (multisampleSource.getNonEmptyGroups (true)); - addModulationAttributes (document, groups, programElement, multisampleSource.getGlobalFilter ()); - // Add up to 4 groups int groupCounter = 0; + final List groups = this.optimizeGroups (multisampleSource.getNonEmptyGroups (true)); for (final IGroup group: groups) { final Element groupElement = XMLUtils.addElement (document, programElement, TALSamplerTag.SAMPLE_LAYER + groupCounter); @@ -181,6 +179,8 @@ private Optional createMetadata (final String folderName, final IMultisa break; } + addModulationAttributes (document, groups, programElement, multisampleSource.getGlobalFilter ()); + return this.createXMLString (document); } @@ -215,18 +215,18 @@ private static void createSample (final Document document, final String folderNa XMLUtils.setIntegerAttribute (sampleElement, TALSamplerTag.END_SAMPLE, stop); XMLUtils.setIntegerAttribute (sampleElement, TALSamplerTag.REVERSE, zone.isReversed () ? 1 : 0); - // transpose // tune in semitones = floor((48.0f * transpose + 0.5f) - 24.0f) + // transpose // tune in semi-tones = floor((48.0f * transpose + 0.5f) - 24.0f) final double tune = zone.getTune (); if (tune != 0) { - // transpose and de-tune are both +-24 semitones, fine tuning is set on the program with - // +-100 cent + // transpose and de-tune are both +-24 semi-tones, fine tuning is set on the program + // with +-100 cent final int transpose = (int) tune; final double fine = tune - transpose; int detune = 0; if (transpose > 24 || transpose < -24) - detune = MathUtils.clamp (transpose > 24 ? transpose - 24 : transpose + 24, -24, 24); + detune = Math.clamp (transpose > 24 ? transpose - 24 : transpose + 24, -24, 24); XMLUtils.setDoubleAttribute (sampleElement, TALSamplerTag.TRANSPOSE, (transpose + 24.0) / 48.0, 4); XMLUtils.setDoubleAttribute (sampleElement, TALSamplerTag.DETUNE, (detune + 24.0) / 48.0, 4); XMLUtils.setDoubleAttribute (programElement, TALSamplerTag.SAMPLE_FINE_TUNE + TALSamplerConstants.LAYERS[groupCounter], (fine + 1.0) / 2.0, 4); @@ -283,15 +283,18 @@ private static void addModulationAttributes (final Document document, final List return; final ISampleZone zone = groups.get (0).getSampleZones ().get (0); + final List modulators = new ArrayList<> (10); // Pitch-bend final int bendUp = Math.abs (zone.getBendUp ()); - final double bendUpValue = bendUp == 0 ? 0.16 : MathUtils.clamp (bendUp / 1200.0, 0, 1.0); + final double bendUpValue = bendUp == 0 ? 0.16 : Math.clamp (bendUp / 1200.0, 0, 1.0); XMLUtils.setDoubleAttribute (programElement, TALSamplerTag.PITCHBEND_RANGE, bendUpValue, 3); final double maxEnvelopeTime = TALSamplerConstants.getMediumSampleLength (groups); - // Add amplitude envelope + ////////////////////////////////////////////////// + // Amplitude + final IEnvelope amplitudeEnvelope = zone.getAmplitudeEnvelopeModulator ().getSource (); setEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_ATTACK, amplitudeEnvelope.getAttackTime (), 0, maxEnvelopeTime); setEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_HOLD, amplitudeEnvelope.getHoldTime (), 0, maxEnvelopeTime); @@ -299,7 +302,13 @@ private static void addModulationAttributes (final Document document, final List setEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_SUSTAIN, amplitudeEnvelope.getSustainLevel (), 0, 1); setEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_RELEASE, amplitudeEnvelope.getReleaseTime (), 0, maxEnvelopeTime); - // Add filter settings + final double ampModDepth = zone.getAmplitudeVelocityModulator ().getDepth (); + if (ampModDepth != 0) + modulators.add (new TALSamplerModulator (TALSamplerModulator.SOURCE_ID_VELOCITY, TALSamplerModulator.DEST_ID_VOLUME_A, ampModDepth)); + + ////////////////////////////////////////////////// + // Filter + if (optFilter.isPresent ()) { final IFilter filter = optFilter.get (); @@ -329,9 +338,15 @@ private static void addModulationAttributes (final Document document, final List // TALSamplerTag.FILTER_KEYBOARD not supported } + + final double cutoffModDepth = filter.getCutoffVelocityModulator ().getDepth (); + if (cutoffModDepth != 0) + modulators.add (new TALSamplerModulator (TALSamplerModulator.SOURCE_ID_VELOCITY, TALSamplerModulator.DEST_ID_CUTOFF, cutoffModDepth)); } - // Add pitch envelope + ////////////////////////////////////////////////// + // Pitch + final IEnvelopeModulator pitchModulator = zone.getPitchModulator (); final double pitchModDepth = pitchModulator.getDepth (); if (pitchModDepth > 0) @@ -344,19 +359,15 @@ private static void addModulationAttributes (final Document document, final List setEnvelopeAttribute (programElement, TALSamplerTag.ADSR_MOD_RELEASE, pitchEnvelope.getReleaseTime (), 0, maxEnvelopeTime); // Envelope 3 needs to be set to modulate the global pitch - final Element modMatrixElement = XMLUtils.addElement (document, programElement, "modmatrix"); - Element entryElement = XMLUtils.addElement (document, modMatrixElement, "entry"); - entryElement.setAttribute ("parameterid", "164"); - entryElement.setAttribute ("modmatrixsourceid", "2"); - XMLUtils.setDoubleAttribute (entryElement, "modmatrixamount", pitchModDepth, 16); - for (int i = 0; i < 9; i++) - { - entryElement = XMLUtils.addElement (document, modMatrixElement, "entry"); - entryElement.setAttribute ("parameterid", "-1"); - entryElement.setAttribute ("modmatrixsourceid", "0"); - entryElement.setAttribute ("modmatrixamount", "0.5"); - } + modulators.add (new TALSamplerModulator (TALSamplerModulator.SOURCE_ID_ENV3, TALSamplerModulator.DEST_ID_MASTER_TUNE, pitchModDepth)); } + + // Create modulator matrix + while (modulators.size () != 10) + modulators.add (new TALSamplerModulator ()); + final Element modMatrixElement = XMLUtils.addElement (document, programElement, TALSamplerTag.MOD_MATRIX); + for (final TALSamplerModulator modulator: modulators) + modulator.createModElements (document, modMatrixElement); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerDetectorTask.java index e267cef..3c904ce 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerDetectorTask.java @@ -24,9 +24,9 @@ import de.mossgrabers.convertwithmoss.core.detector.AbstractDetectorTask; import de.mossgrabers.convertwithmoss.core.detector.DefaultMultisampleSource; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; +import de.mossgrabers.convertwithmoss.core.model.IEnvelopeModulator; import de.mossgrabers.convertwithmoss.core.model.IFilter; import de.mossgrabers.convertwithmoss.core.model.IGroup; -import de.mossgrabers.convertwithmoss.core.model.IEnvelopeModulator; import de.mossgrabers.convertwithmoss.core.model.ISampleZone; import de.mossgrabers.convertwithmoss.core.model.enumeration.LoopType; import de.mossgrabers.convertwithmoss.core.model.implementation.DefaultFilter; @@ -254,19 +254,30 @@ private ISampleZone parseSample (final File parentFolder, final Element programE private static Optional parseModulationAttributes (final Element programElement, final DefaultMultisampleSource multisampleSource) throws IOException { - // Pitch-bend - final int bend = (int) MathUtils.clamp (XMLUtils.getDoubleAttribute (programElement, TALSamplerTag.PITCHBEND_RANGE, 1.0) * 1200.0, 0.0, 1200.0); + final List modulators = parseModulators (programElement); final double maxEnvelopeTime = TALSamplerConstants.getMediumSampleLength (multisampleSource.getGroups ()); - // Add amplitude envelope - final double ampAttach = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_ATTACK, 0, maxEnvelopeTime, 0); + ////////////////////////////////////////////////// + // Amplitude + + final double ampAttack = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_ATTACK, 0, maxEnvelopeTime, 0); final double ampHold = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_HOLD, 0, maxEnvelopeTime, 0); final double ampDecay = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_DECAY, 0, maxEnvelopeTime, 0); final double ampSustain = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_SUSTAIN, 0, 1, 1); final double ampRelease = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_AMP_RELEASE, 0, maxEnvelopeTime, 0); - // Get filter settings + double ampVelocityModAmount = 0.5; + for (final TALSamplerModulator modulator: modulators) + if (modulator.isDestination (TALSamplerModulator.DEST_ID_VOLUME_A) && modulator.isSource (TALSamplerModulator.SOURCE_ID_VELOCITY)) + { + ampVelocityModAmount = modulator.getModAmount (); + break; + } + + ////////////////////////////////////////////////// + // Filter + // We only have a global filter, therefore take only values from the 1st layer Optional optFilter = Optional.empty (); if (XMLUtils.getDoubleAttribute (programElement, TALSamplerTag.FILTER_LAYER_ON + TALSamplerConstants.LAYERS[0], 0) > 0) @@ -294,10 +305,23 @@ private static Optional parseModulationAttributes (final Element progra filterEnvelope.setSustainLevel (getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_VCF_SUSTAIN, 0, 1, 1)); filterEnvelope.setReleaseTime (getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_VCF_RELEASE, 0, maxEnvelopeTime, 0)); } + + for (final TALSamplerModulator modulator: modulators) + if (modulator.isDestination (TALSamplerModulator.DEST_ID_CUTOFF) && modulator.isSource (TALSamplerModulator.SOURCE_ID_VELOCITY)) + { + filter.getCutoffVelocityModulator ().setDepth (modulator.getModAmount ()); + break; + } } } - // Get pitch (modulation) envelope + ////////////////////////////////////////////////// + // Pitch + + // Pitch-bend + final int bend = (int) Math.clamp (XMLUtils.getDoubleAttribute (programElement, TALSamplerTag.PITCHBEND_RANGE, 1.0) * 1200.0, 0.0, 1200.0); + + // Envelope final double pitchAttack = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_MOD_ATTACK, 0, maxEnvelopeTime, 0); final double pitchHold = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_MOD_HOLD, 0, maxEnvelopeTime, 0); final double pitchDecay = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_MOD_DECAY, 0, maxEnvelopeTime, 0); @@ -305,16 +329,15 @@ private static Optional parseModulationAttributes (final Element progra final double pitchRelease = getEnvelopeAttribute (programElement, TALSamplerTag.ADSR_MOD_RELEASE, 0, maxEnvelopeTime, 0); // Envelope 3 needs to be set to modulate the global pitch - final Element modMatrixElement = XMLUtils.getChildElementByName (programElement, "modmatrix"); double globalPitchEnvelopeDepth = -1; - if (modMatrixElement != null) - for (final Element entryElement: XMLUtils.getChildElementsByName (modMatrixElement, "entry", false)) - if (XMLUtils.getIntegerAttribute (entryElement, "parameterid", -1) == 164 && XMLUtils.getIntegerAttribute (entryElement, "modmatrixsourceid", -1) == 2) - { - globalPitchEnvelopeDepth = XMLUtils.getDoubleAttribute (entryElement, "modmatrixamount", 1.0); - break; - } + for (final TALSamplerModulator modulator: modulators) + if (modulator.isSource (TALSamplerModulator.SOURCE_ID_ENV3) || modulator.isDestination (TALSamplerModulator.DEST_ID_TUNE_A, TALSamplerModulator.DEST_ID_MASTER_TUNE)) + { + globalPitchEnvelopeDepth = modulator.getModAmount (); + break; + } + // Set all zones of all groups to the same amplitude and pitch envelope for (final IGroup group: multisampleSource.getGroups ()) for (final ISampleZone zone: group.getSampleZones ()) { @@ -322,12 +345,14 @@ private static Optional parseModulationAttributes (final Element progra zone.setBendDown (bend); final IEnvelope amplitudeEnvelope = zone.getAmplitudeEnvelopeModulator ().getSource (); - amplitudeEnvelope.setAttackTime (ampAttach); + amplitudeEnvelope.setAttackTime (ampAttack); amplitudeEnvelope.setHoldTime (ampHold); amplitudeEnvelope.setDecayTime (ampDecay); amplitudeEnvelope.setSustainLevel (ampSustain); amplitudeEnvelope.setReleaseTime (ampRelease); + zone.getAmplitudeVelocityModulator ().setDepth (ampVelocityModAmount); + if (globalPitchEnvelopeDepth > 0) { final IEnvelopeModulator pitchModulator = zone.getPitchModulator (); @@ -346,6 +371,17 @@ private static Optional parseModulationAttributes (final Element progra } + private static List parseModulators (final Element soundShapeElement) + { + final List modulators = new ArrayList<> (); + final Element modulationElement = XMLUtils.getChildElementByName (soundShapeElement, TALSamplerTag.MOD_MATRIX); + if (modulationElement != null) + for (final Element modulationEntryElement: XMLUtils.getChildElementsByName (modulationElement, TALSamplerTag.MOD_MATRIX_ENTRY, false)) + modulators.add (new TALSamplerModulator (modulationEntryElement)); + return modulators; + } + + private static double getEnvelopeAttribute (final Element element, final String attribute, final double minimum, final double maximum, final double defaultValue) { final double value = XMLUtils.getDoubleAttribute (element, attribute, defaultValue); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerModulator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerModulator.java new file mode 100644 index 0000000..ad91051 --- /dev/null +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerModulator.java @@ -0,0 +1,133 @@ +// Written by Jürgen Moßgraber - mossgrabers.de +// (c) 2019-2024 +// Licensed under LGPLv3 - http://www.gnu.org/licenses/lgpl-3.0.txt + +package de.mossgrabers.convertwithmoss.format.tal; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import de.mossgrabers.tools.XMLUtils; + + +/** + * A TAL Sampler modulator entry. + * + * @author Jürgen Moßgraber + */ +public class TALSamplerModulator +{ + /** Parameter ID for Cutoff. */ + public static int DEST_ID_CUTOFF = 7; + /** Parameter ID for Volume of Layer A. */ + public static int DEST_ID_VOLUME_A = 62; + /** Parameter ID for Tune of Layer A. */ + public static final int DEST_ID_TUNE_A = 71; + /** Parameter ID for Master Tune. */ + public static final int DEST_ID_MASTER_TUNE = 164; + + /** Source ID for Velocity. */ + public static int SOURCE_ID_VELOCITY = 6; + /** Source ID for Envelope 3 (Modulation Envelope). */ + public static final int SOURCE_ID_ENV3 = 2; + + private final int source; + private final int destination; + private final double amount; + + + /** + * Default constructor. + */ + public TALSamplerModulator () + { + this (-1, 0, 0); + } + + + /** + * Constructor. + * + * @param source The source ID + * @param destination The destination ID + * @param amount The amount in the range of [-1..1] + */ + public TALSamplerModulator (final int source, final int destination, final double amount) + { + this.source = source; + this.destination = destination; + this.amount = (amount + 1.0) / 2.0; + } + + + /** + * Constructor. + * + * @param modulationEntryElement The modulator entry element + */ + public TALSamplerModulator (final Element modulationEntryElement) + { + this.source = XMLUtils.getIntegerAttribute (modulationEntryElement, TALSamplerTag.MOD_MATRIX_SOURCE_ID, -1); + this.destination = XMLUtils.getIntegerAttribute (modulationEntryElement, TALSamplerTag.MOD_MATRIX_PARAMETER_ID, 0); + this.amount = XMLUtils.getDoubleAttribute (modulationEntryElement, TALSamplerTag.MOD_MATRIX_AMOUNT, 0.5); + } + + + /** + * Creates a modulation entry from this modulator. + * + * @param document The document to which the element belongs + * @param modulationElement The modulation element to which to add the entry + */ + public void createModElements (final Document document, final Element modulationElement) + { + final Element entryElement = XMLUtils.addElement (document, modulationElement, "entry"); + XMLUtils.setIntegerAttribute (entryElement, TALSamplerTag.MOD_MATRIX_SOURCE_ID, this.source); + XMLUtils.setIntegerAttribute (entryElement, TALSamplerTag.MOD_MATRIX_PARAMETER_ID, this.destination); + XMLUtils.setDoubleAttribute (entryElement, TALSamplerTag.MOD_MATRIX_AMOUNT, this.amount, 6); + } + + + /** + * Test if one of the given IDs matches the source ID of this modulator. + * + * @param sourceIDs The source IDs to match + * @return True if one of the source IDs matches the parsed source ID + */ + public boolean isSource (final int... sourceIDs) + { + return matchTags (this.source, sourceIDs); + } + + + /** + * Test if one of the given IDs matches the destination ID of this modulator. + * + * @param destinationIDs The destination IDs to match + * @return True if one of the destination IDs matches the parsed destination ID + */ + public boolean isDestination (final int... destinationIDs) + { + return matchTags (this.destination, destinationIDs); + } + + + /** + * Get the modulation amount. + * + * @return The modulation amount in the range of [-1..1] + */ + public double getModAmount () + { + return this.amount * 2.0 - 1.0; + } + + + private static boolean matchTags (final int id, final int... ids) + { + for (final int t: ids) + if (t == id) + return true; + return false; + } +} diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerTag.java b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerTag.java index 4f0f884..cea030a 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerTag.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/tal/TALSamplerTag.java @@ -12,148 +12,160 @@ public class TALSamplerTag { /** The root tag. */ - public static final String ROOT = "tal"; + public static final String ROOT = "tal"; /** The programs tag. */ - public static final String PROGRAMS = "programs"; + public static final String PROGRAMS = "programs"; /** The program tag. */ - public static final String PROGRAM = "program"; + public static final String PROGRAM = "program"; /** The sample layer tag. */ - public static final String SAMPLE_LAYER = "samplelayer"; - /** The multi samples tag. */ - public static final String MULTISAMPLES = "multisamples"; + public static final String SAMPLE_LAYER = "samplelayer"; + /** The multi-samples tag. */ + public static final String MULTISAMPLES = "multisamples"; /** The sample tag. */ - public static final String MULTISAMPLE = "multisample"; + public static final String MULTISAMPLE = "multisample"; /** The current program attribute tag. */ - public static final String ROOT_CUR_PROGRAM = "curprogram"; + public static final String ROOT_CUR_PROGRAM = "curprogram"; /** The version tag. */ - public static final String ROOT_VERSION = "version"; + public static final String ROOT_VERSION = "version"; /** The program name attribute tag. */ - public static final String PROGRAM_NAME = "programname"; + public static final String PROGRAM_NAME = "programname"; /** The layer enabled attribute tag. */ - public static final String PROGRAM_LAYER_ON = "sampleenabled"; + public static final String PROGRAM_LAYER_ON = "sampleenabled"; /** The number of voices which can be played. */ - public static final String PROGRAM_NUM_VOICES = "numvoices"; + public static final String PROGRAM_NUM_VOICES = "numvoices"; /** The pitchbend range. */ - public static final String PITCHBEND_RANGE = "globalpitchbendrange"; + public static final String PITCHBEND_RANGE = "globalpitchbendrange"; - /** The multi sample URL tag. */ - public static final String MULTISAMPLE_URL = "url"; + /** The multi-sample URL tag. */ + public static final String MULTISAMPLE_URL = "url"; /** The start tag sample attribute. */ - public static final String START_SAMPLE = "startsample"; + public static final String START_SAMPLE = "startsample"; /** The end tag sample attribute. */ - public static final String END_SAMPLE = "endsample"; + public static final String END_SAMPLE = "endsample"; /** The root note tag sample attribute. */ - public static final String ROOT_NOTE = "rootkey"; + public static final String ROOT_NOTE = "rootkey"; /** The low note tag sample attribute. */ - public static final String LO_NOTE = "lowkey"; + public static final String LO_NOTE = "lowkey"; /** The high note tag sample attribute. */ - public static final String HI_NOTE = "highkey"; + public static final String HI_NOTE = "highkey"; /** The low velocity tag sample attribute. */ - public static final String LO_VEL = "velocitystart"; + public static final String LO_VEL = "velocitystart"; /** The high velocity tag sample attribute. */ - public static final String HI_VEL = "velocityend"; + public static final String HI_VEL = "velocityend"; /** The volume tag on different levels. */ - public static final String VOLUME = "volume"; + public static final String VOLUME = "volume"; /** The sample panorama attribute. */ - public static final String PANORAMA = "pan"; + public static final String PANORAMA = "pan"; /** The layer transpose attribute. */ - public static final String LAYER_TRANSPOSE = "layertranspose"; + public static final String LAYER_TRANSPOSE = "layertranspose"; /** The tune sample attribute. */ - public static final String SAMPLE_TUNE = "sampletune"; + public static final String SAMPLE_TUNE = "sampletune"; /** The fine tune sample attribute. */ - public static final String SAMPLE_FINE_TUNE = "samplefinetune"; + public static final String SAMPLE_FINE_TUNE = "samplefinetune"; /** The sample transpose attribute. */ - public static final String TRANSPOSE = "transpose"; + public static final String TRANSPOSE = "transpose"; /** The transpose tag sample attribute. */ - public static final String DETUNE = "detune"; + public static final String DETUNE = "detune"; /** The pitch key tracking tag sample attribute (0: no tracking, 1 tracking enabled). */ - public static final String PITCH_KEY_TRACK = "track"; + public static final String PITCH_KEY_TRACK = "track"; /** The loop enabled tag sample attribute. */ - public static final String LOOP_ENABLED = "loopenabled"; + public static final String LOOP_ENABLED = "loopenabled"; /** The loop start tag sample attribute. */ - public static final String LOOP_START = "loopstartsample"; + public static final String LOOP_START = "loopstartsample"; /** The loop end tag sample attribute. */ - public static final String LOOP_END = "loopendsample"; + public static final String LOOP_END = "loopendsample"; /** The loop alternate attribute tag. */ - public static final String LOOP_ALTERNATE = "pingpongloop"; + public static final String LOOP_ALTERNATE = "pingpongloop"; /** The sample reverse attribute. */ - public static final String REVERSE = "reverse"; + public static final String REVERSE = "reverse"; /** Fade in samples attribute. */ - public static final String FADE_IN_SAMPLES = "fadeinsamples"; + public static final String FADE_IN_SAMPLES = "fadeinsamples"; /** TAL-Sampler has a few ROM Samples with base waveforms included, always zero. */ - public static final String IS_ROM_SAMPLE = "isromsample"; + public static final String IS_ROM_SAMPLE = "isromsample"; /** The sample slice number attribute, always zero. */ - public static final String SLICE = "slice"; + public static final String SLICE = "slice"; /** Should the phase be inverted? */ - public static final String PHASE_INVERSE = "phaseinverse"; + public static final String PHASE_INVERSE = "phaseinverse"; /** Invert stereo: 1 -> normal; 0 -> inverted stereo output. */ - public static final String STEREO_INVERSE = "stereoinverse"; + public static final String STEREO_INVERSE = "stereoinverse"; /** Is the group muted? */ - public static final String MUTE_GROUP = "mutegroup"; + public static final String MUTE_GROUP = "mutegroup"; /** The global amplitude envelope attack attribute. */ - public static final String ADSR_AMP_ATTACK = "adsrampattack"; + public static final String ADSR_AMP_ATTACK = "adsrampattack"; /** The global amplitude envelope hold attribute. */ - public static final String ADSR_AMP_HOLD = "adsramphold"; + public static final String ADSR_AMP_HOLD = "adsramphold"; /** The global amplitude envelope decay attribute. */ - public static final String ADSR_AMP_DECAY = "adsrampdecay"; + public static final String ADSR_AMP_DECAY = "adsrampdecay"; /** The global amplitude envelope sustain attribute. */ - public static final String ADSR_AMP_SUSTAIN = "adsrampsustain"; + public static final String ADSR_AMP_SUSTAIN = "adsrampsustain"; /** The global amplitude envelope release attribute. */ - public static final String ADSR_AMP_RELEASE = "adsramprelease"; + public static final String ADSR_AMP_RELEASE = "adsramprelease"; /** The global filter envelope attack attribute. */ - public static final String ADSR_VCF_ATTACK = "adsrvcfattack"; + public static final String ADSR_VCF_ATTACK = "adsrvcfattack"; /** The global filter envelope hold attribute. */ - public static final String ADSR_VCF_HOLD = "adsrvcfhold"; + public static final String ADSR_VCF_HOLD = "adsrvcfhold"; /** The global filter envelope decay attribute. */ - public static final String ADSR_VCF_DECAY = "adsrvcfdecay"; + public static final String ADSR_VCF_DECAY = "adsrvcfdecay"; /** The global filter envelope sustain attribute. */ - public static final String ADSR_VCF_SUSTAIN = "adsrvcfsustain"; + public static final String ADSR_VCF_SUSTAIN = "adsrvcfsustain"; /** The global filter envelope release attribute. */ - public static final String ADSR_VCF_RELEASE = "adsrvcfrelease"; + public static final String ADSR_VCF_RELEASE = "adsrvcfrelease"; /** The global modulation (pitch) envelope attack attribute. */ - public static final String ADSR_MOD_ATTACK = "adsrmodattack"; + public static final String ADSR_MOD_ATTACK = "adsrmodattack"; /** The global modulation (pitch) envelope hold attribute. */ - public static final String ADSR_MOD_HOLD = "adsrmodhold"; + public static final String ADSR_MOD_HOLD = "adsrmodhold"; /** The global modulation (pitch) envelope decay attribute. */ - public static final String ADSR_MOD_DECAY = "adsrmoddecay"; + public static final String ADSR_MOD_DECAY = "adsrmoddecay"; /** The global modulation (pitch) envelope sustain attribute. */ - public static final String ADSR_MOD_SUSTAIN = "adsrmodsustain"; + public static final String ADSR_MOD_SUSTAIN = "adsrmodsustain"; /** The global modulation (pitch) envelope release attribute. */ - public static final String ADSR_MOD_RELEASE = "adsrmodrelease"; + public static final String ADSR_MOD_RELEASE = "adsrmodrelease"; /** The amplitude envelope attack attribute. */ - public static final String AMP_ENV_ATTACK = "attack"; + public static final String AMP_ENV_ATTACK = "attack"; /** The amplitude envelope decay attribute. */ - public static final String AMP_ENV_DECAY = "decay"; + public static final String AMP_ENV_DECAY = "decay"; /** The amplitude envelope sustain attribute. */ - public static final String AMP_ENV_SUSTAIN = "sustain"; + public static final String AMP_ENV_SUSTAIN = "sustain"; /** The amplitude envelope release attribute. */ - public static final String AMP_ENV_RELEASE = "release"; + public static final String AMP_ENV_RELEASE = "release"; /** The filter cutoff attribute. */ - public static final String FILTER_CUTOFF = "filtercutoff"; + public static final String FILTER_CUTOFF = "filtercutoff"; /** The filter resonance attribute. */ - public static final String FILTER_RESONANCE = "filterresonance"; + public static final String FILTER_RESONANCE = "filterresonance"; /** The filter mode attribute. */ - public static final String FILTER_MODE = "filtermode"; + public static final String FILTER_MODE = "filtermode"; /** The filter keyboard tracking attribute. [0..1] -> -100%..100% */ - public static final String FILTER_KEYBOARD = "filterkeyboardvalue"; + public static final String FILTER_KEYBOARD = "filterkeyboardvalue"; /** The filter envelope intensity attribute. [0..1] -> -100%..100% */ - public static final String FILTER_ENVELOPE = "filterenvelope"; + public static final String FILTER_ENVELOPE = "filterenvelope"; /** The filter layer on/off attribute. */ - public static final String FILTER_LAYER_ON = "filterlayer"; + public static final String FILTER_LAYER_ON = "filterlayer"; + + /** The modulation matrix tag. */ + public static final String MOD_MATRIX = "modmatrix"; + /** The modulation matrix entry tag. */ + public static final String MOD_MATRIX_ENTRY = "entry"; + + /** The modulation matrix source ID attribute. */ + public static final String MOD_MATRIX_SOURCE_ID = "modmatrixsourceid"; + /** The modulation matrix source ID attribute. */ + public static final String MOD_MATRIX_AMOUNT = "modmatrixamount"; + /** The modulation matrix parameter ID attribute. */ + public static final String MOD_MATRIX_PARAMETER_ID = "parameterid"; /** diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxCreator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxCreator.java index 02a8be5..fde74f0 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxCreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxCreator.java @@ -22,7 +22,6 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.creator.AbstractCreator; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; import de.mossgrabers.convertwithmoss.core.model.IEnvelopeModulator; @@ -500,9 +499,11 @@ private static void addGroupModulationAttributes (final Document document, final // The whole group can be delayed which is basically the same as the amplitude delay soundshapeElement.setAttribute (TX16WxTag.GROUP_DELAY, formatTime (amplitudeEnvelope.getDelayTime ())); - final double sustain = amplitudeEnvelope.getSustainLevel (); - final String sustainLevel = sustain < 0 ? "1" : Double.toString (MathUtils.clamp (sustain, 0, 1)); + ////////////////////////////////////////////// + // Amplitude + final double sustain = amplitudeEnvelope.getSustainLevel (); + final String sustainLevel = sustain < 0 ? "1" : Double.toString (Math.clamp (sustain, 0, 1)); aegElement.setAttribute (TX16WxTag.AMP_ENV_ATTACK, formatTime (amplitudeEnvelope.getAttackTime ())); aegElement.setAttribute (TX16WxTag.AMP_ENV_LEVEL1, "0 dB"); aegElement.setAttribute (TX16WxTag.AMP_ENV_DECAY1, formatTime (amplitudeEnvelope.getHoldTime ())); @@ -514,12 +515,18 @@ private static void addGroupModulationAttributes (final Document document, final XMLUtils.setDoubleAttribute (aegElement, TX16WxTag.AMP_ENV_DECAY2_SHAPE, amplitudeEnvelope.getDecaySlope (), 6); XMLUtils.setDoubleAttribute (aegElement, TX16WxTag.AMP_ENV_RELEASE_SHAPE, amplitudeEnvelope.getReleaseSlope (), 6); + final double ampVelocityDepth = zone.getAmplitudeVelocityModulator ().getDepth (); + if (ampVelocityDepth != 0) + XMLUtils.setDoubleAttribute (soundshapeElement, TX16WxTag.AMP_VELOCITY, ampVelocityDepth, 6); + // Pitch-bend int pitchbend = Math.abs (zone.getBendUp ()); pitchbend = pitchbend <= 0 ? 200 : pitchbend; addModulationEntry (document, modulationElement, "Pitchbend", "Pitch", pitchbend + "Ct"); - // Add filter settings + ////////////////////////////////////////////// + // Filter + final Optional optFilter = zone.getFilter (); if (optFilter.isPresent ()) { @@ -555,9 +562,15 @@ private static void addGroupModulationAttributes (final Document document, final XMLUtils.setDoubleAttribute (envElement, TX16WxTag.ENV_SHAPE3, amplitudeEnvelope.getReleaseSlope (), 6); addModulationEntry (document, modulationElement, "ENV1", "Filter 1 Freq", (int) (filterModDepth * IEnvelope.MAX_ENVELOPE_DEPTH) + "Ct"); } + + final double filterVelocityDepth = filter.getCutoffVelocityModulator ().getDepth (); + if (filterVelocityDepth != 0) + addModulationEntry (document, modulationElement, "Vel", "Filter 1 Freq", (int) (filterVelocityDepth * IEnvelope.MAX_ENVELOPE_DEPTH) + "Ct"); } - // Add pitch modulator + ////////////////////////////////////////////// + // Pitch + final IEnvelopeModulator pitchModulator = zone.getPitchModulator (); final double pitchModDepth = pitchModulator.getDepth (); if (pitchModDepth != 0) diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxDetectorTask.java index 110d20f..0c32fe2 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxDetectorTask.java @@ -193,7 +193,7 @@ private Map parseSamples (final Element topElement, final S sampleName = URLDecoder.decode (sampleName, StandardCharsets.UTF_8).replace ("\\", File.separator); final int height = this.levelsOfDirectorySearch.getSelectionModel ().getSelectedItem ().intValue (); - final File sampleFile = findSampleFile (parentFile, previousFolder, sampleName, height); + final File sampleFile = this.findSampleFile (parentFile, previousFolder, sampleName, height); if (!sampleFile.exists ()) { this.notifier.logError ("IDS_NOTIFY_ERR_SAMPLE_DOES_NOT_EXIST", sampleFile.getAbsolutePath ()); @@ -277,7 +277,7 @@ private Optional parseGroups (final Element programElement, final Map parseGroup (final DefaultGroup group, final Element groupElement, final Map sampleMap, final Map soundShapeElementMap) { final double groupVolumeOffset = this.parseVolume (groupElement, TX16WxTag.VOLUME); - final double groupPanoramaOffset = parsePanorama (groupElement, TX16WxTag.PANORAMA); + final double groupPanoramaOffset = parsePercentage (groupElement, TX16WxTag.PANORAMA); double groupTuningOffset = XMLUtils.getIntegerAttribute (groupElement, TX16WxTag.TUNING_COARSE, 0); groupTuningOffset += XMLUtils.getIntegerAttribute (groupElement, TX16WxTag.TUNING_FINE, 0) / 100.0; @@ -290,12 +290,16 @@ private Optional parseGroup (final DefaultGroup group, final Element gr int pitchbend = -1; IEnvelope ampEnvelope = null; + double ampVelocity = 0; final String soundShape = groupElement.getAttribute (TX16WxTag.SOUND_SHAPE); if (soundShape != null && !soundShape.isBlank ()) { final Element soundShapeElement = soundShapeElementMap.get (soundShape); if (soundShapeElement != null) { + // Velocity modulation for amplitude + ampVelocity = parsePercentage (soundShapeElement, TX16WxTag.AMP_VELOCITY); + // Amplitude envelope final Element aegElement = XMLUtils.getChildElementByName (soundShapeElement, TX16WxTag.AMP_ENVELOPE); if (aegElement != null) @@ -311,14 +315,16 @@ private Optional parseGroup (final DefaultGroup group, final Element gr ampEnvelope.setSustainLevel (parseNormalizedVolume (aegElement, TX16WxTag.AMP_ENV_SUSTAIN)); - ampEnvelope.setAttackSlope (parsePanorama (aegElement, TX16WxTag.AMP_ENV_ATTACK_SHAPE)); - ampEnvelope.setDecaySlope (parsePanorama (aegElement, TX16WxTag.AMP_ENV_DECAY2_SHAPE)); - ampEnvelope.setReleaseSlope (parsePanorama (aegElement, TX16WxTag.AMP_ENV_RELEASE_SHAPE)); + ampEnvelope.setAttackSlope (parsePercentage (aegElement, TX16WxTag.AMP_ENV_ATTACK_SHAPE)); + ampEnvelope.setDecaySlope (parsePercentage (aegElement, TX16WxTag.AMP_ENV_DECAY2_SHAPE)); + ampEnvelope.setReleaseSlope (parsePercentage (aegElement, TX16WxTag.AMP_ENV_RELEASE_SHAPE)); } - filter = this.parseFilter (soundShapeElement); - pitchModulator = parsePitchModulator (soundShapeElement); - final int bend = parsePitchbend (soundShapeElement); + final List modulators = parseModulators (soundShapeElement); + + filter = this.parseFilter (soundShapeElement, modulators); + pitchModulator = parsePitchModulator (soundShapeElement, modulators); + final int bend = parsePitchbend (modulators); if (bend > 0) pitchbend = bend; } @@ -346,6 +352,7 @@ private Optional parseGroup (final DefaultGroup group, final Element gr zone.setBendDown (pitchbend); } + zone.getAmplitudeVelocityModulator ().setDepth (ampVelocity); zone.getAmplitudeEnvelopeModulator ().setSource (ampEnvelope); } } @@ -366,7 +373,7 @@ private Optional parseGroup (final DefaultGroup group, final Element gr private void parseZone (final ISampleZone zone, final Element regionElement, final double groupVolumeOffset, final double groupPanoramaOffset, final double groupTuningOffset) { zone.setGain (groupVolumeOffset + this.parseVolume (regionElement, TX16WxTag.ATTENUATION)); - zone.setPanorama (groupPanoramaOffset + parsePanorama (regionElement, TX16WxTag.PANORAMA)); + zone.setPanorama (groupPanoramaOffset + parsePercentage (regionElement, TX16WxTag.PANORAMA)); double tuning = 0; final Element soundOffsetsElement = XMLUtils.getChildElementByName (regionElement, TX16WxTag.SOUND_OFFSETS); @@ -452,9 +459,10 @@ private static void readLoops (final Element sampleElement, final List parseFilter (final Element soundShapeElement) + private Optional parseFilter (final Element soundShapeElement, final List modulators) { Element filterElement = XMLUtils.getChildElementByName (soundShapeElement, TX16WxTag.FILTER); if (filterElement == null) @@ -499,55 +507,40 @@ else if (frequencyValue.endsWith ("hz") || frequencyValue.endsWith ("Hz")) } final DefaultFilter filter = new DefaultFilter (filterType, poles, frequency, resonance); - parseFilterEnvelope (filter, soundShapeElement); + parseFilterModulation (filter, soundShapeElement, modulators); return Optional.of (filter); } /** - * Parse the filter envelope from the modulation section in the sound shape element. + * Parse the filter envelope and velocity modulation from the modulation section in the sound + * shape element. * * @param filter The filter for which to parse the envelope * @param soundShapeElement The sound shape element + * @param modulators The already parsed modulators */ - private static void parseFilterEnvelope (final IFilter filter, final Element soundShapeElement) + private static void parseFilterModulation (final IFilter filter, final Element soundShapeElement, final List modulators) { - final Element modulationElement = XMLUtils.getChildElementByName (soundShapeElement, TX16WxTag.MODULATION); - if (modulationElement == null) - return; - for (final Element modulationEntryElement: XMLUtils.getChildElementsByName (modulationElement, TX16WxTag.MODULATION_ENTRY, false)) + for (final TX16WxModulator modulator: modulators) { - final String modeSource = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_SOURCE); - final String modDestination = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_DESTINATION); - final boolean isEnv1 = "ENV1".equals (modeSource); - final boolean isEnv2 = "ENV2".equals (modeSource); - if ((isEnv1 || isEnv2) && "Filter 1 Freq".equals (modDestination)) - { - final String modAmount = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_AMOUNT); - if (modAmount.endsWith ("Ct")) - { - final IEnvelopeModulator cutoffModulator = filter.getCutoffEnvelopeModulator (); - final double amount = Integer.parseInt (modAmount.substring (0, modAmount.length () - 2).trim ()) / (double) IEnvelope.MAX_ENVELOPE_DEPTH; - cutoffModulator.setDepth (MathUtils.clamp (amount, -1, 1)); + if (!modulator.isDestination ("Filter 1 Freq")) + continue; - final Element envElement = XMLUtils.getChildElementByName (soundShapeElement, isEnv1 ? TX16WxTag.ENVELOPE_1 : TX16WxTag.ENVELOPE_2); - if (envElement != null) - { - final IEnvelope envEnvelope = cutoffModulator.getSource (); - envEnvelope.setAttackTime (parseTime (envElement, TX16WxTag.ENV_TIME1)); - envEnvelope.setDecayTime (parseTime (envElement, TX16WxTag.ENV_TIME2)); - envEnvelope.setReleaseTime (parseTime (envElement, TX16WxTag.ENV_TIME3)); - - envEnvelope.setStartLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL0)); - envEnvelope.setHoldLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL1)); - envEnvelope.setSustainLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL2)); - envEnvelope.setEndLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL3)); - - envEnvelope.setAttackSlope (parsePanorama (envElement, TX16WxTag.ENV_SHAPE1)); - envEnvelope.setDecaySlope (parsePanorama (envElement, TX16WxTag.ENV_SHAPE2)); - envEnvelope.setReleaseSlope (parsePanorama (envElement, TX16WxTag.ENV_SHAPE3)); - } - } + final Optional modAmountAsCent = modulator.getModAmountAsCent (); + if (modAmountAsCent.isEmpty ()) + continue; + final double amount = Math.clamp (modAmountAsCent.get ().intValue () / (double) IEnvelope.MAX_ENVELOPE_DEPTH, -1, 1); + + if (modulator.isSource ("Vel")) + filter.getCutoffVelocityModulator ().setDepth (amount); + else if (modulator.isSource ("ENV1", "ENV2")) + { + final IEnvelopeModulator cutoffModulator = filter.getCutoffEnvelopeModulator (); + cutoffModulator.setDepth (amount); + final Optional envelope = parseEnvelope (soundShapeElement, modulator.isSource ("ENV1") ? TX16WxTag.ENVELOPE_1 : TX16WxTag.ENVELOPE_2); + if (envelope.isPresent ()) + cutoffModulator.setSource (envelope.get ()); } } } @@ -558,82 +551,88 @@ private static void parseFilterEnvelope (final IFilter filter, final Element sou * element. * * @param soundShapeElement The sound shape element + * @param modulators The already parsed modulators * @return The optional pitch modulator */ - private static Optional parsePitchModulator (final Element soundShapeElement) + private static Optional parsePitchModulator (final Element soundShapeElement, final List modulators) { - final Element modulationElement = XMLUtils.getChildElementByName (soundShapeElement, TX16WxTag.MODULATION); - if (modulationElement == null) - return null; - for (final Element modulationEntryElement: XMLUtils.getChildElementsByName (modulationElement, TX16WxTag.MODULATION_ENTRY, false)) - { - final String modeSource = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_SOURCE); - final String modDestination = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_DESTINATION); - final boolean isEnv1 = "ENV1".equals (modeSource); - final boolean isEnv2 = "ENV2".equals (modeSource); - if ((isEnv1 || isEnv2) && ("Pitch".equals (modDestination) || "Pitch (Raw)".equals (modDestination))) + for (final TX16WxModulator modulator: modulators) + if (modulator.isSource ("ENV1", "ENV2") && modulator.isDestination ("Pitch", "Pitch (Raw)")) { - final String modAmount = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_AMOUNT); - if (modAmount.endsWith ("Ct")) + final Optional modAmoundAsCent = modulator.getModAmountAsCent (); + if (modAmoundAsCent.isPresent ()) { final IEnvelopeModulator pitchModulator = new DefaultEnvelopeModulator (0); - final double amount = Integer.parseInt (modAmount.substring (0, modAmount.length () - 2).trim ()) / 4800.0; - pitchModulator.setDepth (MathUtils.clamp (amount, -1, 1)); + final double amount = modAmoundAsCent.get ().intValue () / 4800.0; + pitchModulator.setDepth (Math.clamp (amount, -1, 1)); - final Element envElement = XMLUtils.getChildElementByName (soundShapeElement, isEnv1 ? TX16WxTag.ENVELOPE_1 : TX16WxTag.ENVELOPE_2); - if (envElement != null) + final Optional pitchEnvelope = parseEnvelope (soundShapeElement, modulator.isSource ("ENV1") ? TX16WxTag.ENVELOPE_1 : TX16WxTag.ENVELOPE_2); + if (pitchEnvelope.isPresent ()) { - final IEnvelope envEnvelope = pitchModulator.getSource (); - envEnvelope.setAttackTime (parseTime (envElement, TX16WxTag.ENV_TIME1)); - envEnvelope.setDecayTime (parseTime (envElement, TX16WxTag.ENV_TIME2)); - envEnvelope.setReleaseTime (parseTime (envElement, TX16WxTag.ENV_TIME3)); - - envEnvelope.setStartLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL0)); - envEnvelope.setHoldLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL1)); - envEnvelope.setSustainLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL2)); - envEnvelope.setEndLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL3)); - - envEnvelope.setAttackSlope (parsePanorama (envElement, TX16WxTag.ENV_SHAPE1)); - envEnvelope.setDecaySlope (parsePanorama (envElement, TX16WxTag.ENV_SHAPE2)); - envEnvelope.setReleaseSlope (parsePanorama (envElement, TX16WxTag.ENV_SHAPE3)); - + pitchModulator.setSource (pitchEnvelope.get ()); return Optional.of (pitchModulator); } } } - } return Optional.empty (); } + private static Optional parseEnvelope (final Element parentElement, final String envelopeTag) + { + final Element envElement = XMLUtils.getChildElementByName (parentElement, envelopeTag); + if (envElement == null) + return Optional.empty (); + + final IEnvelope envelope = new DefaultEnvelope (); + envelope.setAttackTime (parseTime (envElement, TX16WxTag.ENV_TIME1)); + envelope.setDecayTime (parseTime (envElement, TX16WxTag.ENV_TIME2)); + envelope.setReleaseTime (parseTime (envElement, TX16WxTag.ENV_TIME3)); + + envelope.setStartLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL0)); + envelope.setHoldLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL1)); + envelope.setSustainLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL2)); + envelope.setEndLevel (parseNormalizedVolume (envElement, TX16WxTag.ENV_LEVEL3)); + + envelope.setAttackSlope (parsePercentage (envElement, TX16WxTag.ENV_SHAPE1)); + envelope.setDecaySlope (parsePercentage (envElement, TX16WxTag.ENV_SHAPE2)); + envelope.setReleaseSlope (parsePercentage (envElement, TX16WxTag.ENV_SHAPE3)); + + return Optional.of (envelope); + } + + /** * Parse the pitch-bend from the modulation section in the sound shape element. * - * @param soundShapeElement The sound shape element + * @param modulators The already parsed modulators * @return The pitch-bend, negative if not found */ - private static int parsePitchbend (final Element soundShapeElement) + private static int parsePitchbend (final List modulators) { - final Element modulationElement = XMLUtils.getChildElementByName (soundShapeElement, TX16WxTag.MODULATION); - if (modulationElement == null) - return -1; - for (final Element modulationEntryElement: XMLUtils.getChildElementsByName (modulationElement, TX16WxTag.MODULATION_ENTRY, false)) - { - final String modeSource = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_SOURCE); - final String modDestination = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_DESTINATION); - if ("Pitchbend".equals (modeSource) && ("Pitch".equals (modDestination) || "Pitch (raw)".equals (modDestination))) + for (final TX16WxModulator modulator: modulators) + if (modulator.isSource ("Pitchbend") && modulator.isDestination ("Pitch", "Pitch (raw)")) { - final String modAmount = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_AMOUNT); - if (modAmount.endsWith ("Ct")) - return Math.abs (Integer.parseInt (modAmount.substring (0, modAmount.length () - 2).trim ())); + final Optional modAmoundAsCent = modulator.getModAmountAsCent (); + if (modAmoundAsCent.isPresent ()) + return Math.abs (modAmoundAsCent.get ().intValue ()); } - } - return -1; } + private static List parseModulators (final Element soundShapeElement) + { + final List modulators = new ArrayList<> (); + final Element modulationElement = XMLUtils.getChildElementByName (soundShapeElement, TX16WxTag.MODULATION); + if (modulationElement != null) + for (final Element modulationEntryElement: XMLUtils.getChildElementsByName (modulationElement, TX16WxTag.MODULATION_ENTRY, false)) + modulators.add (new TX16WxModulator (modulationEntryElement)); + return modulators; + } + + /** * Get the value of a note element. The value can be either an integer MIDI note or a text like * C#5. @@ -710,18 +709,18 @@ private static double parseNormalizedVolume (final Element element, final String } final double value = attribute.endsWith ("%") ? Double.parseDouble (attribute.substring (0, attribute.length () - 1).trim ()) / 100.0 : Double.parseDouble (attribute); - return MathUtils.clamp (value, 0, 1); + return Math.clamp (value, 0, 1); } /** - * Parses a panorama value from the given tag. + * Parses a percentage value from the given tag. * - * @param element The element which contains the volume attribute - * @param tag The tag name of the attribute containing the volume - * @return The panorama in the range of [-1..1] + * @param element The element which contains the percentage attribute + * @param tag The tag name of the attribute containing the percentage + * @return The percentage in the range of [-1..1] */ - private static double parsePanorama (final Element element, final String tag) + private static double parsePercentage (final Element element, final String tag) { String attribute = element.getAttribute (tag); if (attribute == null) @@ -738,7 +737,7 @@ private static double parsePanorama (final Element element, final String tag) else // The value is in the range of [-1..1] value = Double.parseDouble (attribute); - return MathUtils.clamp (value, -1.0, 1.0); + return Math.clamp (value, -1.0, 1.0); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxModulator.java b/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxModulator.java new file mode 100644 index 0000000..b4fb864 --- /dev/null +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxModulator.java @@ -0,0 +1,81 @@ +// Written by Jürgen Moßgraber - mossgrabers.de +// (c) 2019-2024 +// Licensed under LGPLv3 - http://www.gnu.org/licenses/lgpl-3.0.txt + +package de.mossgrabers.convertwithmoss.format.tx16wx; + +import java.util.Optional; + +import org.w3c.dom.Element; + + +/** + * A TX16Wx modulator entry. + * + * @author Jürgen Moßgraber + */ +public class TX16WxModulator +{ + private final String source; + private final String destination; + private final String amount; + + + /** + * Constructor. + * + * @param modulationEntryElement The modulator entry element + */ + public TX16WxModulator (final Element modulationEntryElement) + { + this.source = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_SOURCE); + this.destination = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_DESTINATION); + this.amount = modulationEntryElement.getAttribute (TX16WxTag.MODULATION_AMOUNT); + } + + + /** + * Test if one of the given tags matches the source tag of this modulator. + * + * @param sourceTags The source tags to match + * @return True if one of the source tags matches the parsed source tag + */ + public boolean isSource (final String... sourceTags) + { + return matchTags (this.source, sourceTags); + } + + + /** + * Test if one of the given tags matches the destination tag of this modulator. + * + * @param destinationTags The destination tags to match + * @return True if one of the destination tags matches the parsed destination tag + */ + public boolean isDestination (final String... destinationTags) + { + return matchTags (this.destination, destinationTags); + } + + + /** + * Get the modulation amount if its unit is cents ('Ct'). + * + * @return The cents if present and the unit is cents + */ + public Optional getModAmountAsCent () + { + if (this.amount != null && this.amount.endsWith ("Ct")) + return Optional.of (Integer.valueOf (this.amount.substring (0, this.amount.length () - 2).trim ())); + return Optional.empty (); + } + + + private static boolean matchTags (final String tag, final String... tags) + { + for (final String t: tags) + if (t.equals (tag)) + return true; + return false; + } +} diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxTag.java b/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxTag.java index 6592d78..7a0131a 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxTag.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/tx16wx/TX16WxTag.java @@ -96,6 +96,8 @@ public class TX16WxTag /** The high velocity tag sample attribute. */ public static final String HI_VEL = "tx:high-vel"; + /** The amplitude velocity modulation tag. */ + public static final String AMP_VELOCITY = "tx:velocity"; /** The amplitude envelope tag. */ public static final String AMP_ENVELOPE = "tx:aeg"; /** The amplitude envelope attack attribute. */ 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 d4a290b..045298f 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 @@ -18,7 +18,6 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.creator.AbstractCreator; import de.mossgrabers.convertwithmoss.core.creator.DestinationAudioFormat; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; @@ -204,8 +203,8 @@ private static void storeMultisample (final IMultisampleSource multisampleSource param.write (out); // Write resource(s) - for (int i = 0; i < sampleMaps.size (); i++) - out.write (sampleMaps.get (i).getBytes ()); + for (final String sampleMap: sampleMaps) + out.write (sampleMap.getBytes ()); } } @@ -289,7 +288,7 @@ private static List createSampleMaps (final List groups, final S /** * Reduces the groups of the multi-sample to a maximum of 3. The sample zones of all other * groups are added to the 3rd group. - * + * * @param groups The groups * @return The reduced groups */ @@ -300,10 +299,8 @@ private static List reduceGroups (final List groups) // Add all sample zones of groups 4..last to group 3 final IGroup lastGroup = groups.get (2); for (int i = 3; i < groups.size (); i++) - { for (final ISampleZone zone: groups.get (i).getSampleZones ()) lastGroup.addSampleZone (zone); - } // Remove groups 4..last final int count = groups.size () - 3; for (int i = 0; i < count; i++) @@ -333,7 +330,7 @@ private static List createParameters (final List g parameters.add (new WaldorfQpatParameter ("Osc" + groupIndex + "FinePitch", "+0.0 cents", 0.5f)); // Osc1PitchBendRange: [0..48] ~ [-24..24] - final int pitchbend = MathUtils.clamp (firstZone.getBendUp (), -24, 24); + final int pitchbend = Math.clamp (firstZone.getBendUp (), -24, 24); parameters.add (new WaldorfQpatParameter ("Osc" + groupIndex + "PitchBendRange", (pitchbend < 0 ? "-" : "+") + pitchbend, pitchbend + 24)); // Osc1Keytrack: [0..1] ~ [-200..200] @@ -350,7 +347,7 @@ private static List createParameters (final List g if (volumeDB == Double.NEGATIVE_INFINITY) volumeStr = "-inf dB"; else - volumeStr = ((volumeDB < 0 ? "-" : "+") + String.format (Locale.US, "%.3f dB", Double.valueOf (volumeDB))); + volumeStr = (volumeDB < 0 ? "-" : "+") + String.format (Locale.US, "%.3f dB", Double.valueOf (volumeDB)); parameters.add (new WaldorfQpatParameter ("Osc" + groupIndex + "Vol", volumeStr, (float) volume)); // Osc1Pan: [0..1] ~ [L..R] @@ -377,7 +374,7 @@ private static List createParameters (final List g { createFilterParameters (parameters, firstZone.getFilter ()); - IEnvelopeModulator amplitudeEnvelopeModulator = firstZone.getAmplitudeEnvelopeModulator (); + final IEnvelopeModulator amplitudeEnvelopeModulator = firstZone.getAmplitudeEnvelopeModulator (); final IEnvelope envelope = amplitudeEnvelopeModulator.getSource (); createEnvelope (parameters, envelope, "AmpEnv", "AmpEnv"); @@ -409,7 +406,7 @@ private static void createPitchEnvelopeModulator (final List parameters, // xxxEnvAttack parameters.add (new WaldorfQpatParameter (prefix + "Attack", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (0)), 0)); // xxxEnvDecay - final double decayTime = MathUtils.clamp (envelope.getAttackTime (), 0, 60); - parameters.add (new WaldorfQpatParameter (prefix + "Decay", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (decayTime)), (float) (convertFromTime (decayTime)))); + final double decayTime = Math.clamp (envelope.getAttackTime (), 0, 60); + parameters.add (new WaldorfQpatParameter (prefix + "Decay", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (decayTime)), (float) convertFromTime (decayTime))); } else { // xxxEnvDelay - final double delayTime = MathUtils.clamp (envelope.getDelayTime (), 0, 2); - parameters.add (new WaldorfQpatParameter (prefix + "Delay", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (delayTime)), (float) (convertFromDelayTime (delayTime)))); + final double delayTime = Math.clamp (envelope.getDelayTime (), 0, 2); + parameters.add (new WaldorfQpatParameter (prefix + "Delay", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (delayTime)), (float) convertFromDelayTime (delayTime))); // xxxEnvAttack - final double attackTime = MathUtils.clamp (envelope.getAttackTime (), 0, 60); - parameters.add (new WaldorfQpatParameter (prefix + "Attack", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (attackTime)), (float) (convertFromTime (attackTime)))); + final double attackTime = Math.clamp (envelope.getAttackTime (), 0, 60); + parameters.add (new WaldorfQpatParameter (prefix + "Attack", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (attackTime)), (float) convertFromTime (attackTime))); // xxxEnvDecay - final double decayTime = MathUtils.clamp (envelope.getDecayTime (), 0, 60); - parameters.add (new WaldorfQpatParameter (prefix + "Decay", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (decayTime)), (float) (convertFromTime (decayTime)))); + final double decayTime = Math.clamp (envelope.getDecayTime (), 0, 60); + parameters.add (new WaldorfQpatParameter (prefix + "Decay", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (decayTime)), (float) convertFromTime (decayTime))); } // xxxEnvRelease - final double releaseTime = MathUtils.clamp (envelope.getReleaseTime (), 0, 60); - parameters.add (new WaldorfQpatParameter (prefix + "Release", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (releaseTime)), (float) (convertFromTime (releaseTime)))); + final double releaseTime = Math.clamp (envelope.getReleaseTime (), 0, 60); + parameters.add (new WaldorfQpatParameter (prefix + "Release", String.format (Locale.US, FORMAT_SECONDS, Double.valueOf (releaseTime)), (float) convertFromTime (releaseTime))); // xxxEnvSustain double sustainLevel = envelope.getSustainLevel (); if (sustainLevel == -1) sustainLevel = isPitch ? 0 : 1; - parameters.add (new WaldorfQpatParameter (prefix + "Sustain", String.format (Locale.US, "%.2f", Double.valueOf (sustainLevel * 100.0)) + " %", (float) (sustainLevel))); + parameters.add (new WaldorfQpatParameter (prefix + "Sustain", String.format (Locale.US, "%.2f", Double.valueOf (sustainLevel * 100.0)) + " %", (float) sustainLevel)); if (isPitch && envelope.getStartLevel () != 0) { @@ -625,7 +622,7 @@ private static double convertFromDecibels (final double db) { if (db == Double.NEGATIVE_INFINITY) return 0; - return MathUtils.clamp (Math.pow (10, db / 40), 0, 1); + return Math.clamp (Math.pow (10, db / 40), 0, 1); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatDetectorTask.java index a003cbc..1a738f0 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatDetectorTask.java @@ -21,7 +21,6 @@ import de.mossgrabers.convertwithmoss.core.IMultisampleSource; import de.mossgrabers.convertwithmoss.core.INotifier; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.detector.AbstractDetectorTask; import de.mossgrabers.convertwithmoss.core.detector.DefaultMultisampleSource; import de.mossgrabers.convertwithmoss.core.model.IEnvelope; @@ -123,7 +122,7 @@ protected List readFile (final File file) */ private List parseFile (final InputStream in, final File file) throws FormatException, IOException { - String name = FileUtils.getNameWithoutType (file); + final String name = FileUtils.getNameWithoutType (file); final String [] parts = AudioFileUtils.createPathParts (file.getParentFile (), this.sourceFolder, name); in.mark (in.available () + 1); @@ -213,7 +212,7 @@ private List parseFile (final InputStream in, final File fil /** * Read all 3 resource headers. - * + * * @param in The input stream to read from * @return The resource headers * @throws IOException Could not read the headers @@ -273,7 +272,7 @@ private void applyParameters (final IGroup [] groups, final Map findPitchEnvelopeModMatrixEntry (fin { // MatrixDstX: [2] "Osc1 Pitch" [3] "Osc2 Pitch" [4] "Osc3 Pitch" final WaldorfQpatParameter destParam = parameters.get ("MatrixDst" + i); - if (destParam != null && (destParam.value == oscIndex + 1.0)) + if (destParam != null && destParam.value == oscIndex + 1.0) { // MatrixAmount1 final WaldorfQpatParameter amountParam = parameters.get ("MatrixAmount" + i); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatParameter.java b/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatParameter.java index 285eb75..052694c 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatParameter.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/waldorf/qpat/WaldorfQpatParameter.java @@ -25,7 +25,7 @@ public class WaldorfQpatParameter /** * Constructor. - * + * * @param name The name of the parameter * @param hint The descriptive text of the value * @param value The value of the parameter diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/wav/WavFileSampleData.java b/src/main/java/de/mossgrabers/convertwithmoss/format/wav/WavFileSampleData.java index 670d677..258ae77 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/wav/WavFileSampleData.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/wav/WavFileSampleData.java @@ -13,7 +13,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import de.mossgrabers.convertwithmoss.core.MathUtils; import de.mossgrabers.convertwithmoss.core.model.IMetadata; import de.mossgrabers.convertwithmoss.core.model.ISampleLoop; import de.mossgrabers.convertwithmoss.core.model.ISampleZone; @@ -171,7 +170,7 @@ public void addZoneData (final ISampleZone zone, final boolean addRootKey, final if (zone.getTune () == 0) { - final double tune = MathUtils.clamp (sampleChunk.getMIDIPitchFractionAsCents () / 100.0, -0.5, 0.5); + final double tune = Math.clamp (sampleChunk.getMIDIPitchFractionAsCents () / 100.0, -0.5, 0.5); zone.setTune (tune); // Root note needs to be updated as well! if (tune < 0) diff --git a/src/main/java/de/mossgrabers/convertwithmoss/ui/ConvertWithMossApp.java b/src/main/java/de/mossgrabers/convertwithmoss/ui/ConvertWithMossApp.java index bfb2d92..131ce15 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/ui/ConvertWithMossApp.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/ui/ConvertWithMossApp.java @@ -49,8 +49,6 @@ import de.mossgrabers.convertwithmoss.format.tal.TALSamplerDetector; import de.mossgrabers.convertwithmoss.format.tx16wx.TX16WxCreator; import de.mossgrabers.convertwithmoss.format.tx16wx.TX16WxDetector; -import de.mossgrabers.convertwithmoss.format.waldorf.qpat.WaldorfQpatCreator; -import de.mossgrabers.convertwithmoss.format.waldorf.qpat.WaldorfQpatDetector; import de.mossgrabers.convertwithmoss.format.wav.WavCreator; import de.mossgrabers.convertwithmoss.format.wav.WavDetector; import de.mossgrabers.tools.ui.AbstractFrame; @@ -168,7 +166,7 @@ public ConvertWithMossApp () throws EndApplicationException new SfzDetector (this), new Sf2Detector (this), new TALSamplerDetector (this), - new WaldorfQpatDetector (this), + // new WaldorfQpatDetector (this), new WavDetector (this)// , // new YamahaYsfcDetector (this) }; @@ -190,7 +188,7 @@ public ConvertWithMossApp () throws EndApplicationException new SfzCreator (this), new Sf2Creator (this), new TALSamplerCreator (this), - new WaldorfQpatCreator (this), + // new WaldorfQpatCreator (this), new WavCreator (this) }; } diff --git a/src/main/resources/Strings.properties b/src/main/resources/Strings.properties index 1aac0bc..61b6435 100644 --- a/src/main/resources/Strings.properties +++ b/src/main/resources/Strings.properties @@ -1,4 +1,4 @@ -TITLE=ConvertWithMoss 10.5.0 +TITLE=ConvertWithMoss 10.2.0 ################################################################################## # diff --git a/src/main/resources/de/mossgrabers/convertwithmoss/templates/nki/Kontakt1_02_Group.xml b/src/main/resources/de/mossgrabers/convertwithmoss/templates/nki/Kontakt1_02_Group.xml index 74f17e9..7e210bf 100644 --- a/src/main/resources/de/mossgrabers/convertwithmoss/templates/nki/Kontakt1_02_Group.xml +++ b/src/main/resources/de/mossgrabers/convertwithmoss/templates/nki/Kontakt1_02_Group.xml @@ -99,5 +99,12 @@ + + + + + + +