diff --git a/CHANGELOG.md b/CHANGELOG.md index 982363f..7fc56bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changes +## 11.7.0 + +* New: Writing of samples can now be cancelled as well. +* Fixed: Logger text is now cleared regularily to prevent a crash. To have the log still available, all messages are now logged into a file ConvertWithMoss.log which is created in the output directory. +* Kontakt + * Fixed: Regression: Reading Kontakt 5-7 file lists were broken. + * Fixed: NCW files are now only read when needed for writing and the memory is freed up directly afterwards to support NKIs which reference a very large amounts of NCW files. +* Korg KMP + * New: KSF files which reference another KSF file are now read properly. + * New: Reading: Applied +12dB option. + * New: KMP/KSF files which contain SKIPPEDSAMPLE as a filename are now ignored (conversion was canceled previously). +* Sample Files + * New: Notify about the number of sample files found in a folder before the mapping starts. + ## 11.6.0 * EXS24 diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.jar.md5 b/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.jar.md5 deleted file mode 100644 index a4cb325..0000000 --- a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.jar.md5 +++ /dev/null @@ -1 +0,0 @@ -1f82009d0a3586c0808d7aed1428215b \ No newline at end of file diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.jar.sha1 b/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.jar.sha1 deleted file mode 100644 index 6f3f4ad..0000000 --- a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -765c5b28d03a453b90367a2dd76b497aa13bf129 \ No newline at end of file diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.pom.md5 b/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.pom.md5 deleted file mode 100644 index 6fe1718..0000000 --- a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.pom.md5 +++ /dev/null @@ -1 +0,0 @@ -d68a627f4ac72078abfce0c389567b29 \ No newline at end of file diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.pom.sha1 b/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.pom.sha1 deleted file mode 100644 index 3e9febc..0000000 --- a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.pom.sha1 +++ /dev/null @@ -1 +0,0 @@ -4c307a1103fd9ca401332545214fda01ff85e4bc \ No newline at end of file diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.jar b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.jar similarity index 81% rename from maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.jar rename to maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.jar index 2a190ad..3bf1f62 100644 Binary files a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.jar and b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.jar differ diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.jar.md5 b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.jar.md5 new file mode 100644 index 0000000..77d381d --- /dev/null +++ b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.jar.md5 @@ -0,0 +1 @@ +972610b7530023e1a5b75b8e6b46fe6a \ No newline at end of file diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.jar.sha1 b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.jar.sha1 new file mode 100644 index 0000000..78c4e43 --- /dev/null +++ b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.jar.sha1 @@ -0,0 +1 @@ +677ce031293c57c3229b55c17ad498490d7251a2 \ No newline at end of file diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.pom b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.pom similarity index 95% rename from maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.pom rename to maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.pom index 2d16212..b84894e 100644 --- a/maven-local-repository/de/mossgrabers/uitools/1.5.2/uitools-1.5.2.pom +++ b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.pom @@ -7,7 +7,7 @@ de.mossgrabers jar UiTools - 1.5.2 + 1.5.3 UTF-8 @@ -32,15 +32,10 @@ javafx-web 23.0.1 - - org.fxmisc.richtext - richtextfx - 0.11.3 - org.junit.jupiter junit-jupiter-api - 5.11.3 + 5.11.4 test diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.pom.md5 b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.pom.md5 new file mode 100644 index 0000000..bbf7cf1 --- /dev/null +++ b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.pom.md5 @@ -0,0 +1 @@ +f9aa23a4bdeb813432459f86bcdedadc \ No newline at end of file diff --git a/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.pom.sha1 b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.pom.sha1 new file mode 100644 index 0000000..c58d42e --- /dev/null +++ b/maven-local-repository/de/mossgrabers/uitools/1.5.3/uitools-1.5.3.pom.sha1 @@ -0,0 +1 @@ +2ebe38dea9dd275dacf8d264cab4fecb9474fd6b \ No newline at end of file diff --git a/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml b/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml index 9c5cc49..987e6a1 100644 --- a/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml +++ b/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml @@ -3,10 +3,10 @@ de.mossgrabers uitools - 1.5.2 + 1.5.3 - 1.5.2 + 1.5.3 - 20241118164256 + 20250103142649 diff --git a/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml.md5 b/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml.md5 index fc982ba..d34befa 100644 --- a/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml.md5 +++ b/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml.md5 @@ -1 +1 @@ -ad0ddfd3b1da31526f04a4f60f100e6e \ No newline at end of file +99fe126702a5f194ca99e9925f671db9 \ No newline at end of file diff --git a/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml.sha1 b/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml.sha1 index 26f3242..2d36216 100644 --- a/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml.sha1 +++ b/maven-local-repository/de/mossgrabers/uitools/maven-metadata.xml.sha1 @@ -1 +1 @@ -5bf395d679ed1decc83d2c358543f9ee768a839f \ No newline at end of file +a981d4efab038fc6a085c8a661aa0a2098d032e1 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 50a4886..3798007 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 de.mossgrabers convertwithmoss - 11.6.0 + 11.7.0 jar ConvertWithMoss @@ -49,7 +49,7 @@ uitools de.mossgrabers - 1.5.2 + 1.5.3 diff --git a/src/main/java/de/mossgrabers/convertwithmoss/core/AbstractCoreTask.java b/src/main/java/de/mossgrabers/convertwithmoss/core/AbstractCoreTask.java index d0b42af..0e95d1a 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/AbstractCoreTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/AbstractCoreTask.java @@ -81,10 +81,14 @@ public void shutdown () } - /** {@inheritDoc} */ - @Override - public void cancel () + protected void notifyProgress () { - // Intentionally empty + this.notifier.log ("IDS_NOTIFY_PROGRESS"); + } + + + protected void notifyNewline () + { + this.notifier.log ("IDS_NOTIFY_LINE_FEED"); } } 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 80fcea9..a38667d 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/creator/AbstractCreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/creator/AbstractCreator.java @@ -92,6 +92,8 @@ public abstract class AbstractCreator extends AbstractCoreTask implements ICreat private boolean updateSampleChunk = false; private boolean removeJunkChunks = false; + protected boolean isCancelled = false; + // Combine into library options protected CheckBox combineIntoOneLibrary = null; protected TextField combinationFilename = null; @@ -126,6 +128,22 @@ public boolean wantsMultipleFiles () } + /** {@inheritDoc} */ + @Override + public void cancel () + { + this.isCancelled = true; + } + + + /** {@inheritDoc} */ + @Override + public void clearCancelled () + { + this.isCancelled = false; + } + + /** * Add the UI elements for the combination of several multi-sample source into 1 library. * @@ -579,6 +597,9 @@ protected List writeSamples (final File sampleFolder, final IMultisampleSo final List sampleZones = group.getSampleZones (); for (int zoneIndex = 0; zoneIndex < sampleZones.size (); zoneIndex++) { + if (this.isCancelled) + return writtenFiles; + final ISampleZone zone = sampleZones.get (zoneIndex); final File file = new File (sampleFolder, this.createSampleFilename (zone, zoneIndex, fileEnding)); @@ -644,6 +665,9 @@ protected List writeSamples (final File sampleFolder, final IMultisampleSo final List sampleZones = group.getSampleZones (); for (int zoneIndex = 0; zoneIndex < sampleZones.size (); zoneIndex++) { + if (this.isCancelled) + return writtenFiles; + final ISampleZone zone = sampleZones.get (zoneIndex); final File file = new File (sampleFolder, this.createSampleFilename (zone, zoneIndex, extension)); @@ -929,18 +953,6 @@ private static void putUncompressedEntry (final ZipOutputStream zipOutputStream, } - private void notifyProgress () - { - this.notifier.log ("IDS_NOTIFY_PROGRESS"); - } - - - private void notifyNewline () - { - this.notifier.log ("IDS_NOTIFY_LINE_FEED"); - } - - /** * Adds options add or update certain WAV chunks. * diff --git a/src/main/java/de/mossgrabers/convertwithmoss/core/creator/ICreator.java b/src/main/java/de/mossgrabers/convertwithmoss/core/creator/ICreator.java index 7a54512..428d206 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/creator/ICreator.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/creator/ICreator.java @@ -45,4 +45,10 @@ public interface ICreator extends ICoreTask * @return Returns true if the creator wants to combine several files */ boolean wantsMultipleFiles (); + + + /** + * Clears the cancelled state. Call before each run. + */ + void clearCancelled (); } 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 92d93a2..ea88138 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/core/detector/AbstractDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/core/detector/AbstractDetectorTask.java @@ -159,7 +159,7 @@ protected Boolean call () throws Exception /** - * Wait a bit. + * Check for task cancellation. * * @return The thread was cancelled if true */ @@ -636,4 +636,16 @@ private File findSampleFileRecursively (final File folder, final String fileName return null; } + + + protected void notifyProgress () + { + this.notifier.log ("IDS_NOTIFY_PROGRESS"); + } + + + protected void notifyNewline () + { + this.notifier.log ("IDS_NOTIFY_LINE_FEED"); + } } 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 6f50513..5e283fa 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 @@ -368,7 +368,7 @@ public void setVelocityCrossfadeHigh (final int crossfadeHigh) @Override public void setGain (final double gain) { - this.gain = Math.clamp (gain, -12.0, 12.0); + this.gain = Math.clamp (gain, 0.125, 24.0); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/file/StreamUtils.java b/src/main/java/de/mossgrabers/convertwithmoss/file/StreamUtils.java index 573f921..2737733 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/file/StreamUtils.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/file/StreamUtils.java @@ -889,6 +889,38 @@ public static void padBytes (final OutputStream out, final int numBytes, final i } + /** + * Reads a certain number of bytes from the input stream. The number of bytes is determined from + * the first 4 bytes (32-bit value). + * + * @param in The input stream to read from + * @param isBigEndian True if bytes are stored big-endian otherwise little-endian + * @return The data block without the size bytes + * @throws IOException Could not read + */ + public static byte [] readDataBlock (final InputStream in, final boolean isBigEndian) throws IOException + { + final int size = (int) StreamUtils.readUnsigned32 (in, isBigEndian); + return in.readNBytes (size); + } + + + /** + * Reads a certain number of bytes from the input stream. The number of bytes is determined from + * the first 4 bytes (32-bit value). + * + * @param out The output stream to write to + * @param data The data block without the size bytes + * @param isBigEndian True if bytes are stored big-endian otherwise little-endian + * @throws IOException Could not read + */ + public static void writeDataBlock (final OutputStream out, final byte [] data, final boolean isBigEndian) throws IOException + { + StreamUtils.writeUnsigned32 (out, data.length, isBigEndian); + out.write (data); + } + + /** * Skip exactly N bytes. * diff --git a/src/main/java/de/mossgrabers/convertwithmoss/file/ncw/NcwFile.java b/src/main/java/de/mossgrabers/convertwithmoss/file/ncw/NcwFile.java index 5a2e380..fddabc9 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/file/ncw/NcwFile.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/file/ncw/NcwFile.java @@ -47,6 +47,8 @@ public class NcwFile private int [] [] channelData; private float [] [] channelDataFloat; + private final File ncwFile; + private final Object lazyLoadingLock = new Object (); /** @@ -57,10 +59,7 @@ public class NcwFile */ public NcwFile (final File ncwFile) throws IOException { - try (final FileInputStream stream = new FileInputStream (ncwFile)) - { - this.read (stream); - } + this.ncwFile = ncwFile; } @@ -72,6 +71,7 @@ public NcwFile (final File ncwFile) throws IOException */ public NcwFile (final InputStream inputStream) throws IOException { + this.ncwFile = null; this.read (inputStream); } @@ -80,9 +80,11 @@ public NcwFile (final InputStream inputStream) throws IOException * Get the number of channels. * * @return The number of channels + * @throws IOException Could not read the file */ - public int getChannels () + public int getChannels () throws IOException { + this.lazyLoading (); return this.channels; } @@ -91,9 +93,11 @@ public int getChannels () * Get the number of samples of one channel. * * @return The number of samples + * @throws IOException Could not read the file */ - public int getNumberOfSamples () + public int getNumberOfSamples () throws IOException { + this.lazyLoading (); return this.numberOfSamples; } @@ -102,9 +106,11 @@ public int getNumberOfSamples () * Get the bits per sample. * * @return Bits per sample + * @throws IOException Could not read the file */ - public int getBitsPerSample () + public int getBitsPerSample () throws IOException { + this.lazyLoading (); return this.bitsPerSample; } @@ -113,9 +119,11 @@ public int getBitsPerSample () * Get the sample rate. * * @return The sample rate + * @throws IOException Could not read the file */ - public int getSampleRate () + public int getSampleRate () throws IOException { + this.lazyLoading (); return this.sampleRate; } @@ -126,27 +134,51 @@ public int getSampleRate () * @param outputStream Where to write the WAV file * @throws IOException Could not write */ - public void writeWAV (final OutputStream outputStream) throws IOException + public synchronized void writeWAV (final OutputStream outputStream) throws IOException { - final boolean isFloat = this.channelDataFloat != null; + synchronized (this.lazyLoadingLock) + { + this.lazyLoading (); - final WaveFile wavFile = new WaveFile (this.channels, this.sampleRate, this.bitsPerSample, this.numberOfSamples); - if (isFloat) - wavFile.getFormatChunk ().setCompressionCode (FormatChunk.WAVE_FORMAT_IEEE_FLOAT); - final DataChunk dataChunk = wavFile.getDataChunk (); + final boolean isFloat = this.channelDataFloat != null; + + final WaveFile wavFile = new WaveFile (this.channels, this.sampleRate, this.bitsPerSample, this.numberOfSamples); + if (isFloat) + wavFile.getFormatChunk ().setCompressionCode (FormatChunk.WAVE_FORMAT_IEEE_FLOAT); + final DataChunk dataChunk = wavFile.getDataChunk (); + + final ByteArrayOutputStream bout = new ByteArrayOutputStream (this.channels * (this.bitsPerSample / 8) * this.numberOfSamples); + if (isFloat) + for (int i = 0; i < this.numberOfSamples; i++) + for (int channel = 0; channel < this.channels; channel++) + StreamUtils.writeFloatLE (bout, this.channelDataFloat[channel][i]); + else + for (int i = 0; i < this.numberOfSamples; i++) + for (int channel = 0; channel < this.channels; channel++) + StreamUtils.writeUnsigned (bout, this.channelData[channel][i], this.bitsPerSample, false); + + dataChunk.setData (bout.toByteArray ()); + wavFile.write (outputStream); + + // Dirty workaround to allow garbage collection + this.channelData = null; + this.channelDataFloat = null; + } + } - final ByteArrayOutputStream bout = new ByteArrayOutputStream (this.channels * (this.bitsPerSample / 8) * this.numberOfSamples); - if (isFloat) - for (int i = 0; i < this.numberOfSamples; i++) - for (int channel = 0; channel < this.channels; channel++) - StreamUtils.writeFloatLE (bout, this.channelDataFloat[channel][i]); - else - for (int i = 0; i < this.numberOfSamples; i++) - for (int channel = 0; channel < this.channels; channel++) - StreamUtils.writeUnsigned (bout, this.channelData[channel][i], this.bitsPerSample, false); - dataChunk.setData (bout.toByteArray ()); - wavFile.write (outputStream); + private void lazyLoading () throws IOException + { + synchronized (this.lazyLoadingLock) + { + if (this.channelData != null || this.channelDataFloat != null) + return; + + try (final FileInputStream stream = new FileInputStream (this.ncwFile)) + { + this.read (stream); + } + } } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/file/ncw/NcwFileSampleData.java b/src/main/java/de/mossgrabers/convertwithmoss/file/ncw/NcwFileSampleData.java index 4891f67..ac1856f 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/file/ncw/NcwFileSampleData.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/file/ncw/NcwFileSampleData.java @@ -37,26 +37,26 @@ public NcwFileSampleData (final File file) throws IOException { super (file); - this.handleNcwFile (new NcwFile (file)); + this.ncwFile = new NcwFile (file); } /** - * Constructor. + * Constructor. Used for monoliths. * * @param inputStream The stream from which the file content can be read * @throws IOException Could not read the file */ public NcwFileSampleData (final InputStream inputStream) throws IOException { - this.handleNcwFile (new NcwFile (inputStream)); + this.ncwFile = new NcwFile (inputStream); } - private void handleNcwFile (final NcwFile ncwFile) throws IOException + /** {@inheritDoc} */ + @Override + protected void createAudioMetadata () throws IOException { - this.ncwFile = ncwFile; - final int channels = this.ncwFile.getChannels (); if (channels > 2) throw new IOException (Functions.getMessage ("IDS_NOTIFY_ERR_MONO", Integer.toString (channels), this.sampleFile.getAbsolutePath ())); 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 204a64d..a7aaf53 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KMPFile.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KMPFile.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -186,6 +187,16 @@ public void read (final InputStream inputStream) throws IOException, ParseExcept if (in.available () == 0) break; } + + // Remove all 'skipped sample' zones + final List cleanedZones = new ArrayList<> (); + for (final ISampleZone zone: this.zones) + { + if (!SAMPLE_SKIPPED.equals (zone.getName ())) + cleanedZones.add (zone); + } + this.zones.clear (); + this.zones.addAll (cleanedZones); } @@ -226,23 +237,30 @@ private void readParameterChunk1 (final DataInputStream in) throws IOException, // Filter Cutoff - unused in KMP itself in.readByte (); - final byte [] nBytes = in.readNBytes (12); - final String sampleFilename = new String (nBytes); + String sampleFilename = new String (in.readNBytes (12)); - if (SAMPLE_SKIPPED.equals (sampleFilename)) - this.notifier.logError ("IDS_KMP_ERR_SKIPPED_SAMPLE"); - else if (sampleFilename.startsWith (SAMPLE_INTERNAL)) - try - { - final int internalIndex = Integer.parseInt (sampleFilename.substring (SAMPLE_INTERNAL.length ())); - this.notifier.logError ("IDS_KMP_ERR_INTERNAL_SAMPLE", Integer.toString (internalIndex)); - } - catch (final NumberFormatException ex) + while (sampleFilename != null) + { + if (SAMPLE_SKIPPED.equals (sampleFilename)) { - // All good, not a reference to internal sample memory + zone.setName (SAMPLE_SKIPPED); + this.notifier.log ("IDS_KMP_SKIPPED_SAMPLE"); + break; } - this.readKSFZone (zone, sampleFilename); + if (sampleFilename.startsWith (SAMPLE_INTERNAL)) + try + { + final int internalIndex = Integer.parseInt (sampleFilename.substring (SAMPLE_INTERNAL.length ())); + throw new IOException (Functions.getMessage ("IDS_KMP_ERR_INTERNAL_SAMPLE", Integer.toString (internalIndex))); + } + catch (final NumberFormatException ex) + { + // All good, not a reference to internal sample memory + } + + sampleFilename = this.readKSFZone (zone, sampleFilename); + } } } @@ -261,7 +279,7 @@ private void readParameterChunk2 (final DataInputStream in) throws IOException } - private void readKSFZone (final ISampleZone zone, final String filename) throws IOException, ParseException + private String readKSFZone (final ISampleZone zone, final String filename) throws IOException, ParseException { File ksfFile = new File (this.sampleFolder1, filename); if (!ksfFile.exists ()) @@ -273,7 +291,7 @@ private void readKSFZone (final ISampleZone zone, final String filename) throws try (final FileInputStream stream = new FileInputStream (ksfFile)) { - KSFFile.read (stream, zone); + return KSFFile.read (stream, zone); } } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KSFFile.java b/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KSFFile.java index 40db025..07c05ae 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KSFFile.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/kmp/KSFFile.java @@ -78,10 +78,11 @@ private KSFFile () * * @param inputStream Where to read the file from * @param zone The zone to which to add the KSF data + * @return The referenced KSF file, if any * @throws IOException Could not read the file * @throws ParseException Error during parsing */ - public static void read (final InputStream inputStream, final ISampleZone zone) throws IOException, ParseException + public static String read (final InputStream inputStream, final ISampleZone zone) throws IOException, ParseException { final DataInputStream in = new DataInputStream (inputStream); @@ -138,8 +139,10 @@ public static void read (final InputStream inputStream, final ISampleZone zone) // Bit 7 (0x40): 1 = reverse, 0 = forward // Bit 8 (0x80): 1 = loop off, 0 = loop on final int attributes = in.read (); + + // Boost by 12dB if ((attributes & 1) > 0) - System.out.println ("Boosted!"); + zone.setGain (boostDecibels (zone.getGain (), 12.0)); if ((attributes & 0x10) > 0) throw new ParseException (Functions.getMessage ("IDS_KMP_COMPRESSED_DATA_NOT_SUPPORTED")); @@ -168,12 +171,12 @@ public static void read (final InputStream inputStream, final ISampleZone zone) case KSF_SAMPLE_NAME_ID: assertSize (id, dataSize, KSF_SAMPLE_NAME_SIZE); - combinedName = new String (in.readNBytes (24)); + combinedName = new String (in.readNBytes (KSF_SAMPLE_NAME_SIZE)); break; case KSF_SAMPLE_FILENAME_ID: assertSize (id, dataSize, KSF_SAMPLE_FILENAME_SIZE); - throw new ParseException (Functions.getMessage ("IDS_KMP_ERR_REFERENCED_KSF_NOT_SUPPORTED")); + return new String (in.readNBytes (KSF_SAMPLE_FILENAME_SIZE)); case KSF_SAMPLE_DIVIDED_PARAM_ID, KSF_SAMPLE_DIVIDED_DATA_ID: throw new ParseException (Functions.getMessage ("IDS_KMP_ERR_DISTRIBUTED_KSF_NOT_SUPPORTED")); @@ -190,6 +193,8 @@ public static void read (final InputStream inputStream, final ISampleZone zone) final InMemorySampleData sampleData = new InMemorySampleData (audioMetadata, data); zone.setName (combinedName.trim ()); zone.setSampleData (sampleData); + + return null; } @@ -348,4 +353,20 @@ private static String createSafeFilename (final String filename) final String name = filename.replaceAll ("[\\\\/:*?\"<>|&\\.#]", "_").trim (); return name.length () == 1 ? "NAME" + name : name; } + + + private static double boostDecibels (final double originalDb, final double boostDb) + { + // Convert original dB to linear scale + double originalLinear = Math.pow (10, originalDb / 10); + + // Convert boost dB to linear scale + double boostLinear = Math.pow (10, boostDb / 10); + + // Add the linear values + double newLinear = originalLinear + boostLinear; + + // Convert back to dB + return 10 * Math.log10 (newLinear); + } } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/nki/type/kontakt5/FileList.java b/src/main/java/de/mossgrabers/convertwithmoss/format/nki/type/kontakt5/FileList.java index 02f7931..467f1eb 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/nki/type/kontakt5/FileList.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/nki/type/kontakt5/FileList.java @@ -46,15 +46,15 @@ public void parse (final PresetChunk chunk) throws IOException if (version < 2 || version > 3) throw new IOException (Functions.getMessage ("IDS_NKI5_UNSUPPORTED_FILELIST_EX_VERSION", Integer.toString (version))); - final List files = readFiles (in, version); if (version == 3) { + final List files = readFilesV3 (in); this.specialFiles = files; this.sampleFiles = files; return; } - this.specialFiles = files; + this.specialFiles = readFilesV2 (in); } else { @@ -67,7 +67,7 @@ public void parse (final PresetChunk chunk) throws IOException readFile (in); } - this.sampleFiles = readFiles (in, -1); + this.sampleFiles = readFilesV2 (in); this.readMetadata (in, chunkID); } @@ -90,7 +90,7 @@ private void readMetadata (final ByteArrayInputStream in, final int chunkID) thr for (int i = 0; i < numFiles; i++) StreamUtils.readUnsigned32 (in, false); - this.otherFiles = readFiles (in, -1); + this.otherFiles = readFilesV2 (in); } else // Final padding @@ -99,14 +99,33 @@ private void readMetadata (final ByteArrayInputStream in, final int chunkID) thr /** - * Read several file paths. + * Read several file paths in version 2. * * @param in The input stream to read from - * @param version The version of the Filename List structure * @return The read file paths * @throws IOException Could not read */ - private static List readFiles (final ByteArrayInputStream in, final int version) throws IOException + private static List readFilesV2 (final ByteArrayInputStream in) throws IOException + { + final List files = new ArrayList<> (); + if (in.available () > 0) + { + final int size = StreamUtils.readSigned32 (in, false); + for (int i = 0; i < size; i++) + files.add (readFile (in)); + } + return files; + } + + + /** + * Read several file paths in version 3. + * + * @param in The input stream to read from + * @return The read file paths + * @throws IOException Could not read + */ + private static List readFilesV3 (final ByteArrayInputStream in) throws IOException { final List files = new ArrayList<> (); if (in.available () > 0) @@ -114,14 +133,13 @@ private static List readFiles (final ByteArrayInputStream in, final int final int size = StreamUtils.readSigned32 (in, false); // 00 padding - if (version == 3) - in.skipNBytes (8); + in.skipNBytes (8); for (int i = 0; i < size - 1; i++) { files.add (readFile (in)); - if (version == 3 && in.available () > 0) + if (in.available () > 0) { // Padding - always zero in.skipNBytes (4); @@ -133,8 +151,7 @@ private static List readFiles (final ByteArrayInputStream in, final int } // The last empty entry - 00 padding - if (version == 3) - in.skipNBytes (24); + in.skipNBytes (24); } return files; } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/samplefile/SampleFileDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/samplefile/SampleFileDetectorTask.java index 87a0a9c..7ba182b 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/samplefile/SampleFileDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/samplefile/SampleFileDetectorTask.java @@ -109,7 +109,10 @@ protected List readFile (final File folder) if (files.length == 0) continue; + this.notifier.log ("IDS_NOTIFY_FOUND_RAW_FILES", Integer.toString (files.length), sampleFileType.getName ()); + // Analyze all files + int outputCount = 0; final List sampleData = new ArrayList<> (files.length); for (final File file: files) { @@ -120,6 +123,10 @@ protected List readFile (final File folder) try { sampleData.add (this.createSampleData (file)); + this.notifyProgress (); + outputCount++; + if (outputCount % 80 == 0) + this.notifyNewline (); } catch (final IOException ex) { @@ -128,6 +135,8 @@ protected List readFile (final File folder) } } + this.notifyNewline (); + sources.addAll (this.createMultisample (sampleFileType, folder, sampleData)); } diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcDetectorTask.java b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcDetectorTask.java index b5c4d1d..033ac23 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcDetectorTask.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcDetectorTask.java @@ -59,7 +59,9 @@ public class YamahaYsfcDetectorTask extends AbstractDetectorTask ".x7a", ".x8u", // MODX / MODX+ ".x8l", - ".x8a" + ".x8a", + ".y2l", // Montage M + ".y2u" }; private static final int SAMPLE_RESOLUTION = 16; @@ -88,8 +90,20 @@ protected List readFile (final File file) try { final YsfcFile ysfcFile = new YsfcFile (file); - this.notifier.log ("IDS_YSFC_FOUND_TYPE", getWorkstationName (ysfcFile), ysfcFile.getVersionStr ()); - return this.createMultisample (ysfcFile); + this.notifier.log ("IDS_YSFC_FOUND_TYPE", ysfcFile.getVersion ().getTitle (), ysfcFile.getVersionStr ()); + + final List keyGroups = this.createMultisamplesFromKeygroups (ysfcFile); + + // If there are no Performances, create directly from key-groups + final YamahaYsfcChunk epfmChunk = ysfcFile.getChunks ().get (YamahaYsfcChunk.ENTRY_LIST_PERFORMANCE); + if (epfmChunk == null) + { + this.notifier.log ("IDS_YSFC_NO_PERFORMANCES"); + return keyGroups; + } + + // TODO + return keyGroups; } catch (final IOException ex) { @@ -99,29 +113,14 @@ protected List readFile (final File file) } - private static String getWorkstationName (final YsfcFile ysfcFile) - { - final int version = ysfcFile.getVersion (); - if (version <= 101) - return "Motif XS"; - if (version == 102) - return "Motif XF"; - if (version == 103) - return "MOXF"; - if (version >= 500) - return "MODX"; - return "Montage"; - } - - /** - * Create a multi-sample from the chunk data. + * Create multi-samples from the key-group/wave chunk data. * * @param ysfcFile The YSFC source file * @return The multi-sample(s) * @throws IOException COuld not read the multi-sample */ - private List createMultisample (final YsfcFile ysfcFile) throws IOException + private List createMultisamplesFromKeygroups (final YsfcFile ysfcFile) throws IOException { final Map chunks = ysfcFile.getChunks (); @@ -177,7 +176,7 @@ private List createMultisample (final YsfcFile ysfcFile) thr } - private static IGroup createSampleZones (final List keyBanks, final List waveDataItems, final String name, final int version) throws IOException + private static IGroup createSampleZones (final List keyBanks, final List waveDataItems, final String name, final YamahaYsfcVersion version) throws IOException { final DefaultGroup group = new DefaultGroup ("Layer"); @@ -230,7 +229,7 @@ private static IGroup createSampleZones (final List keyBanks, else sampleData.setSampleData (WaveFile.interleaveChannels (data, dataRight, SAMPLE_RESOLUTION)); - keybankIndex += version < 400 ? 1 : 2; + keybankIndex += version.isVersion1 () ? 1 : 2; waveDataIndex += 2; } } @@ -319,17 +318,16 @@ private static List readWaveData (final byte [] dwimDataArra * Reads all the key-bank data items from the given data array. * * @param dwfmDataArray The array to read from - * @param version The format version of the key-bank, e.g. 404 for version 4.0.4 + * @param version The format version of the YSFC file * @return The parsed wave metadata items * @throws IOException Could not read the data */ - private static List readKeyBanks (final byte [] dwfmDataArray, final int version) throws IOException + private static List readKeyBanks (final byte [] dwfmDataArray, final YamahaYsfcVersion version) throws IOException { final List keyBanks = new ArrayList<> (); final ByteArrayInputStream dwfmContentStream = new ByteArrayInputStream (dwfmDataArray); - final boolean isVersion1 = version < 400; // numberOfKeyBank - StreamUtils.readUnsigned16 (dwfmContentStream, isVersion1); + StreamUtils.readUnsigned16 (dwfmContentStream, version.isVersion1 ()); dwfmContentStream.skipNBytes (2); while (dwfmContentStream.available () > 0) keyBanks.add (new YamahaYsfcKeybank (dwfmContentStream, version)); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcKeybank.java b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcKeybank.java index 93765b9..1c99eba 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcKeybank.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcKeybank.java @@ -56,7 +56,7 @@ public YamahaYsfcKeybank () * @param version The format version of the key-bank, e.g. 404 for version 4.0.4 * @throws IOException Could not read the entry item */ - public YamahaYsfcKeybank (final InputStream in, final int version) throws IOException + public YamahaYsfcKeybank (final InputStream in, final YamahaYsfcVersion version) throws IOException { this.read (in, version); } @@ -69,10 +69,10 @@ public YamahaYsfcKeybank (final InputStream in, final int version) throws IOExce * @param version The format version of the key-bank, e.g. 404 for version 4.0.4 * @throws IOException Could not read the entry item */ - public void read (final InputStream in, final int version) throws IOException + public void read (final InputStream in, final YamahaYsfcVersion version) throws IOException { - final boolean isVersion1 = version < 400; - final boolean isMotif = version < 103; + final boolean isVersion1 = version.isVersion1 (); + final boolean isMotif = version.isMotif (); final boolean isBigEndian = isVersion1 && !isMotif; this.keyRangeLower = in.read (); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcPartElement.java b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcPartElement.java new file mode 100644 index 0000000..ef21186 --- /dev/null +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcPartElement.java @@ -0,0 +1,65 @@ +// 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.yamaha.ysfc; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import de.mossgrabers.convertwithmoss.file.StreamUtils; + + +/** + * An element of a part in a performance. + * + * @author Jürgen Moßgraber + */ +public class YamahaYsfcPartElement +{ + private byte [] dataBlock; + + + /** + * Constructor which reads the performance from the input stream. + * + * @param in The input stream + * @param version The format version of the YSFC file + * @throws IOException Could not read the entry item + */ + public YamahaYsfcPartElement (final InputStream in, final YamahaYsfcVersion version) throws IOException + { + this.read (in, version); + } + + + /** + * Read a performance from the input stream. + * + * @param in The input stream + * @param version The format version of the YSFC file + * @throws IOException Could not read the entry item + */ + public void read (final InputStream in, final YamahaYsfcVersion version) throws IOException + { + // TODO ... + this.dataBlock = StreamUtils.readDataBlock (in, true); + final InputStream elementData = new ByteArrayInputStream (dataBlock); + // TODO ... + } + + + /** + * Write a performance to the output stream. + * + * @param out The output stream + * @throws IOException Could not write the entry item + */ + public void write (final OutputStream out) throws IOException + { + // TODO + StreamUtils.writeDataBlock (out, this.dataBlock, true); + } +} diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcPerformance.java b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcPerformance.java new file mode 100644 index 0000000..76c2820 --- /dev/null +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcPerformance.java @@ -0,0 +1,312 @@ +// 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.yamaha.ysfc; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import de.mossgrabers.convertwithmoss.file.StreamUtils; +import de.mossgrabers.tools.StringUtils; +import de.mossgrabers.tools.ui.Functions; + + +/** + * A performance which is the metadata description of a sample in YSFC terms. + * + * @author Jürgen Moßgraber + */ +public class YamahaYsfcPerformance +{ + private String name; + private final List parts = new ArrayList<> (); + private final YamahaYsfcVersion sourceVersion; + + private byte [] reverbBlock; + private byte [] variationBlock; + private byte [] masterEqBlock; + private byte [] masterEffectBlock; + private byte [] commonParameters; + private byte [] sceneData; + private byte [] controlBoxes; + private byte [] adPart; + private byte [] digitalInputPart; + private byte [] playSettings; + private final String [] assignableKnobs = new String [8]; + final List allParts = new ArrayList<> (); + + + /** + * Default constructor. + * + * @param version The version to use + * @throws IOException Could not read the default parameters for the requested version + */ + public YamahaYsfcPerformance (final YamahaYsfcVersion version) throws IOException + { + this.sourceVersion = version; + if (version != YamahaYsfcVersion.MONTAGE) + throw new IOException (Functions.getMessage ("IDS_YSFC_VERSION_NOT_SUPPORTED", version.getTitle ())); + final byte [] data = Functions.rawFileFor ("de/mossgrabers/convertwithmoss/templates/ysfc/MontageDPFM.bin"); + this.read (new ByteArrayInputStream (data)); + } + + + /** + * Constructor which reads the performance from the input stream. + * + * @param in The input stream + * @param version The format version of the YSFC file + * @throws IOException Could not read the entry item + */ + public YamahaYsfcPerformance (final InputStream in, final YamahaYsfcVersion version) throws IOException + { + this.sourceVersion = version; + this.read (in); + } + + + /** + * Read a performance from the input stream. + * + * @param in The input stream + * @throws IOException Could not read the entry item + */ + public void read (final InputStream in) throws IOException + { + this.readCommon (new ByteArrayInputStream (StreamUtils.readDataBlock (in, true))); + this.readEffects (in); + this.readParts (in); + } + + + private void readCommon (final InputStream in) throws IOException + { + this.name = StreamUtils.readASCII (in, 21).trim (); + final int pos = this.name.indexOf (0); + if (pos >= 0) + this.name = this.name.substring (0, pos); + + // Common Parameters + this.commonParameters = in.readNBytes (43); + + // Scene 1-8 + if (this.sourceVersion == YamahaYsfcVersion.MONTAGE) + this.sceneData = in.readNBytes (8 * 11); + else // MODX + this.sceneData = in.readNBytes (8 * 21); + + // Assignable Knob 1-8 + for (int i = 0; i < 8; i++) + this.assignableKnobs[i] = StreamUtils.readASCII (in, 17).trim (); + + // Control Box 1-16 + this.controlBoxes = in.readNBytes (16 * 9); + } + + + private void readEffects (final InputStream in) throws IOException + { + this.reverbBlock = StreamUtils.readDataBlock (in, true); + this.variationBlock = StreamUtils.readDataBlock (in, true); + this.masterEqBlock = StreamUtils.readDataBlock (in, true); + this.masterEffectBlock = StreamUtils.readDataBlock (in, true); + } + + + private void readParts (final InputStream in) throws IOException + { + final int numberOfParts = (int) StreamUtils.readUnsigned32 (in, true); + + for (int i = 0; i < numberOfParts; i++) + this.allParts.add (new YamahaYsfcPerformancePart (new ByteArrayInputStream (StreamUtils.readDataBlock (in, true)), this.sourceVersion)); + + // Skip AD + Digital input parts + this.adPart = StreamUtils.readDataBlock (in, true); + this.digitalInputPart = StreamUtils.readDataBlock (in, true); + + // Read all elements + + // Only keep sample based parts (0 = AWM, 1 = AWM Drum, 2 = FM) + for (int i = 0; i < numberOfParts; i++) + { + final int partType = (int) StreamUtils.readUnsigned32 (in, true); + switch (partType) + { + // AWM + case 0, 1: + // 8 for plain AWM or 73 for drums + final int numberOfElements = (int) StreamUtils.readUnsigned32 (in, true); + final YamahaYsfcPerformancePart part = this.allParts.get (i); + this.parts.add (part); + for (int el = 0; el < numberOfElements; el++) + part.addElement (new YamahaYsfcPartElement (in, this.sourceVersion)); + break; + + // FM - not used + case 2: + final int numberOfOperators = (int) StreamUtils.readUnsigned32 (in, true); + StreamUtils.readDataBlock (in, true); + for (int op = 0; op < numberOfOperators; op++) + StreamUtils.readDataBlock (in, true); + break; + + default: + throw new IOException (Functions.getMessage ("IDS_YSFC_UNKNOWN_PART_TYPE", Integer.toString (partType))); + } + } + + // Super knob & ARP settings + this.playSettings = in.readAllBytes (); + } + + + /** + * Write a performance to the output stream. + * + * @param out The output stream + * @throws IOException Could not write the entry item + */ + public void write (final OutputStream out) throws IOException + { + this.writeCommon (out); + this.writeEffects (out); + this.writeParts (out); + } + + + private void writeCommon (final OutputStream out) throws IOException + { + final ByteArrayOutputStream arrayOut = new ByteArrayOutputStream (); + + StreamUtils.writeASCII (arrayOut, StringUtils.optimizeName (this.name, 20), 21); + + arrayOut.write (this.commonParameters); + arrayOut.write (this.sceneData); + + // Assignable Knob 1-8 + for (int i = 0; i < 8; i++) + StreamUtils.writeASCII (arrayOut, StringUtils.rightPadSpaces (StringUtils.optimizeName (this.assignableKnobs[i], 16), 16), 17); + + // Control Box 1-16 + arrayOut.write (this.controlBoxes); + + StreamUtils.writeDataBlock (out, arrayOut.toByteArray (), true); + } + + + private void writeEffects (final OutputStream out) throws IOException + { + StreamUtils.writeDataBlock (out, this.reverbBlock, true); + StreamUtils.writeDataBlock (out, this.variationBlock, true); + StreamUtils.writeDataBlock (out, this.masterEqBlock, true); + StreamUtils.writeDataBlock (out, this.masterEffectBlock, true); + } + + + private void writeParts (final OutputStream out) throws IOException + { + StreamUtils.writeUnsigned32 (out, this.allParts.size (), true); + + for (final YamahaYsfcPerformancePart part: this.allParts) + part.write (out); + + StreamUtils.writeDataBlock (out, this.adPart, true); + StreamUtils.writeDataBlock (out, this.digitalInputPart, true); + + for (final YamahaYsfcPerformancePart part: this.allParts) + { + StreamUtils.writeUnsigned32 (out, part.type, true); + + // Always 8 AWM elements! + StreamUtils.writeUnsigned32 (out, 8, true); + for (final YamahaYsfcPartElement element: part.getElements ()) + element.write (out); + } + + out.write (this.playSettings); + } + + + public static void main (final String [] args) + { + try (final FileOutputStream out = new FileOutputStream (new File ("C:\\Users\\mos\\Desktop\\result.bin"))) + { + final YamahaYsfcPerformance performance = new YamahaYsfcPerformance (YamahaYsfcVersion.MONTAGE); + performance.write (out); + } + catch (final IOException e) + { + e.printStackTrace (); + } + } + + + public static void main2 (final String [] args) + { + final String filenameX7U = "C:\\Privat\\Programming\\ConvertWithMoss\\Testdateien\\YamahaYSFC\\X7 - Montage\\01W Atmosphere.X7U"; + final String filenameX8L = "C:\\Privat\\Programming\\ConvertWithMoss\\Testdateien\\YamahaYSFC\\X8 - MODX\\Arilyn.X8L"; + + YsfcFile ysfcFile; + try + { + ysfcFile = new YsfcFile (new File (filenameX7U)); + final Map chunks = ysfcFile.getChunks (); + final YamahaYsfcChunk epfmChunk = chunks.get (YamahaYsfcChunk.ENTRY_LIST_PERFORMANCE); + final YamahaYsfcChunk dpfmChunk = chunks.get (YamahaYsfcChunk.DATA_LIST_PERFORMANCE); + if (epfmChunk == null || dpfmChunk == null) + // TODO + // this.notifier.logError ("IDS_YSFC_NO_MULTISAMPLE_DATA"); + // return Collections.emptyList (); + return; + + final List epfmListChunks = epfmChunk.getEntryListChunks (); + final List dpfmListChunks = dpfmChunk.getDataArrays (); + + if (epfmListChunks.size () != dpfmListChunks.size ()) + // TODO + // this.notifier.logError ("IDS_YSFC_DIFFERENT_NUMBER_OF_WAVEFORM_CHUNKS"); + // return Collections.emptyList (); + return; + + for (int i = 0; i < epfmListChunks.size (); i++) + { + final YamahaYsfcEntry yamahaYsfcEntry = epfmListChunks.get (i); + final byte [] performanceData = dpfmListChunks.get (i); + + final YamahaYsfcPerformance yamahaYsfcPerformance = new YamahaYsfcPerformance (new ByteArrayInputStream (performanceData), ysfcFile.getVersion ()); + } + + } + catch (final IOException e) + { + e.printStackTrace (); + } + } + + + private static void dump (final YamahaYsfcEntry yamahaYsfcEntry, final byte [] performanceData, final int version) + { + // TODO Auto-generated method stub + try + { + Files.write (new File ("C:\\Users\\mos\\Desktop\\Data\\" + yamahaYsfcEntry.getItemTitle () + "DPFM.bin").toPath (), performanceData); + } + catch (final IOException e) + { + // TODO Auto-generated catch block + e.printStackTrace (); + } + + } +} diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcPerformancePart.java b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcPerformancePart.java new file mode 100644 index 0000000..401f3fc --- /dev/null +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcPerformancePart.java @@ -0,0 +1,93 @@ +// 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.yamaha.ysfc; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import de.mossgrabers.convertwithmoss.file.StreamUtils; +import de.mossgrabers.tools.StringUtils; + + +/** + * A part in a performance. + * + * @author Jürgen Moßgraber + */ +public class YamahaYsfcPerformancePart +{ + String name; + int type; + private List elements = new ArrayList<> (); + private byte [] theRest; + + + /** + * Constructor which reads the performance from the input stream. + * + * @param in The input stream + * @param version The format version of the YSFC file + * @throws IOException Could not read the entry item + */ + public YamahaYsfcPerformancePart (final InputStream in, final YamahaYsfcVersion version) throws IOException + { + this.read (in, version); + } + + + /** + * Read a performance from the input stream. + * + * @param in The input stream + * @param version The format version of the YSFC file + * @throws IOException Could not read the entry item + */ + public void read (final InputStream in, final YamahaYsfcVersion version) throws IOException + { + this.name = StreamUtils.readASCII (in, 21).trim (); + final int pos = this.name.indexOf (0); + if (pos >= 0) + this.name = this.name.substring (0, pos); + + this.type = in.read (); + // TODO ... + this.theRest = in.readAllBytes (); + } + + + /** + * Write a performance to the output stream. + * + * @param out The output stream + * @throws IOException Could not write the entry item + */ + public void write (final OutputStream out) throws IOException + { + final ByteArrayOutputStream arrayOut = new ByteArrayOutputStream (); + StreamUtils.writeASCII (arrayOut, StringUtils.rightPadSpaces (StringUtils.optimizeName (this.name, 20), 20), 21); + + arrayOut.write (this.type); + // TODO + arrayOut.write (this.theRest); + + StreamUtils.writeDataBlock (out, arrayOut.toByteArray (), true); + } + + + public void addElement (final YamahaYsfcPartElement element) + { + this.elements.add (element); + } + + + public List getElements () + { + return this.elements; + } +} diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcVersion.java b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcVersion.java new file mode 100644 index 0000000..37f13ca --- /dev/null +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YamahaYsfcVersion.java @@ -0,0 +1,94 @@ +// 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.yamaha.ysfc; + +/** + * Possible YSFC versions. + * + * @author Jürgen Moßgraber + */ +public enum YamahaYsfcVersion +{ + /** 1.0.1 */ + MOTIF_XS("Motif XS"), + /** 1.0.2 */ + MOTIF_XF("Motif XF"), + /** 1.0.3 */ + MOXF("MOXF"), + /** 4.0.4 */ + MONTAGE("Montage"), + /** 4.1.0 */ + MONTAGE_M("Montage M"), + /** 5.0.1 */ + MODX("MODX"), + /** Unknown version. */ + UNKNOWN("Unknown"); + + + private final String title; + + + private YamahaYsfcVersion (final String title) + { + this.title = title; + } + + + /** + * Get the title. + * + * @return The title + */ + public String getTitle () + { + return this.title; + } + + + /** + * Returns true if this is a version 1.0.x version. + * + * @return True if version 1 + */ + public boolean isVersion1 () + { + return this.isMotif () || this == MOXF; + } + + + /** + * Returns true if this is a Motif 1.0.x version. + * + * @return True if Motif version + */ + public boolean isMotif () + { + return this == MOTIF_XF || this == MOTIF_XS; + } + + + /** + * Get the constant from the 3 digit version number. + * + * @param version The version, e.g. version 4.0.5 is 405. + * @return The enumeration constant + */ + public static YamahaYsfcVersion get (final int version) + { + if (version <= 101) + return MOTIF_XS; + if (version == 102) + return MOTIF_XF; + if (version == 103) + return MOXF; + if (version >= 400 && version < 410) + return MONTAGE; + if (version >= 410 && version < 420) + return MONTAGE_M; + if (version >= 500 && version < 510) + return MODX; + return UNKNOWN; + } +} diff --git a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YsfcFile.java b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YsfcFile.java index 3b3f871..3a19a26 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YsfcFile.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/format/yamaha/ysfc/YsfcFile.java @@ -100,18 +100,19 @@ public String getVersionStr () */ public void setVersionStr (final String versionStr) { - this.versionStr = versionStr; + this.versionStr = versionStr.trim (); + this.version = parseVersion (this.versionStr); } /** - * Get the version number as an integer, e.g. 404. + * Get the YSFC version. * - * @return The version number + * @return The version */ - public int getVersion () + public YamahaYsfcVersion getVersion () { - return this.version; + return YamahaYsfcVersion.get (this.version); } @@ -146,8 +147,7 @@ private void read (final InputStream inputStream) throws IOException // The version in the form of 'A.B.C', e.g. '1.0.2'. Older versions may have appended 0xFF // instead of 0x00 - this.versionStr = createAsciiString (inputStream.readNBytes (16)); - this.version = parseVersion (this.versionStr.trim ()); + this.setVersionStr (createAsciiString (inputStream.readNBytes (16))); // The size of the chunk catalog block final int catalogSize = (int) StreamUtils.readUnsigned32 (inputStream, true); diff --git a/src/main/java/de/mossgrabers/convertwithmoss/templates/ysfc/package-info.java b/src/main/java/de/mossgrabers/convertwithmoss/templates/ysfc/package-info.java new file mode 100644 index 0000000..ba45131 --- /dev/null +++ b/src/main/java/de/mossgrabers/convertwithmoss/templates/ysfc/package-info.java @@ -0,0 +1,10 @@ +// Written by Jürgen Moßgraber - mossgrabers.de +// (c) 2019-2024 +// Licensed under LGPLv3 - http://www.gnu.org/licenses/lgpl-3.0.txt + +/** + * Resources folder for YSFC template files. + * + * @author Jürgen Moßgraber + */ +package de.mossgrabers.convertwithmoss.templates.ysfc; \ No newline at end of file diff --git a/src/main/java/de/mossgrabers/convertwithmoss/ui/ConvertWithMossApp.java b/src/main/java/de/mossgrabers/convertwithmoss/ui/ConvertWithMossApp.java index 7729b72..7238c16 100644 --- a/src/main/java/de/mossgrabers/convertwithmoss/ui/ConvertWithMossApp.java +++ b/src/main/java/de/mossgrabers/convertwithmoss/ui/ConvertWithMossApp.java @@ -6,6 +6,7 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -148,10 +149,12 @@ public class ConvertWithMossApp extends AbstractFrame implements INotifier, Cons private Button renameFilePathSelectButton; private final CSVRenameFile csvRenameFile = new CSVRenameFile (); - private final LoggerBoxWeb loggingArea = new LoggerBoxWeb (); + private final LoggerBoxWeb loggingArea = new LoggerBoxWeb (4000); private final TraversalManager traversalManager = new TraversalManager (); private final List collectedSources = new ArrayList<> (); + private FileWriter logWriter; + /** * Main-method. @@ -349,8 +352,7 @@ public void initialise (final Stage stage, final Optional baseTitleOptio this.closeButton = setupButton (exButtonPanel, "Close", "@IDS_EXEC_CLOSE", "@IDS_EXEC_CLOSE_TOOLTIP"); this.closeButton.setOnAction (event -> this.closeExecution ()); - final Parent loggerComponent = this.loggingArea.getComponent (); - this.executePane.setCenter (loggerComponent); + this.executePane.setCenter (this.loggingArea.getComponent ()); this.executePane.setRight (exButtonPanel.getPane ()); this.executePane.setVisible (false); @@ -539,7 +541,12 @@ private void execute (final boolean onlyAnalyse) if (selectedDetector < 0 || !this.detectors[selectedDetector].checkSettings () || !this.detectors[selectedDetector].validateParameters ()) return; - this.loggingArea.clear (); + final int selectedCreator = this.destinationTabPane.getSelectionModel ().getSelectedIndex (); + if (selectedCreator < 0) + return; + this.creators[selectedCreator].clearCancelled (); + + this.clearLog (); this.mainPane.setVisible (false); this.executePane.setVisible (true); @@ -559,6 +566,10 @@ private void cancelExecution () final int selectedDetector = this.sourceTabPane.getSelectionModel ().getSelectedIndex (); if (selectedDetector >= 0) this.detectors[selectedDetector].cancel (); + + final int selectedCreator = this.destinationTabPane.getSelectionModel ().getSelectedIndex (); + if (selectedCreator >= 0) + this.creators[selectedCreator].cancel (); } @@ -753,11 +764,36 @@ private void applyRenaming (final IMultisampleSource multisampleSource) } + private void clearLog () + { + this.loggingArea.clear (); + + try + { + this.logWriter = new FileWriter (new File (this.outputFolder, "ConvertWithMoss.log")); + } + catch (final IOException ex) + { + this.loggingArea.notifyError ("IDS_NOTIFY_ERR_NO_LOG_FILE", ex); + this.logWriter = null; + } + } + + /** {@inheritDoc} */ @Override public void log (final String messageID, final String... replaceStrings) { - this.loggingArea.notify (Functions.getMessage (messageID, replaceStrings)); + this.logText (Functions.getMessage (messageID, replaceStrings)); + } + + + /** {@inheritDoc} */ + @Override + public void logText (final String text) + { + this.loggingArea.notify (text); + this.logToFile (text); } @@ -765,7 +801,9 @@ public void log (final String messageID, final String... replaceStrings) @Override public void logError (final String messageID, final String... replaceStrings) { - this.loggingArea.notifyError (Functions.getMessage (messageID, replaceStrings)); + final String message = Functions.getMessage (messageID, replaceStrings); + this.loggingArea.notifyError (message); + this.logToFile (message); } @@ -773,7 +811,9 @@ public void logError (final String messageID, final String... replaceStrings) @Override public void logError (final String messageID, final Throwable throwable) { - this.loggingArea.notifyError (Functions.getMessage (messageID, throwable)); + final String message = Functions.getMessage (messageID, throwable); + this.loggingArea.notifyError (message); + this.logToFile (message); } @@ -796,14 +836,21 @@ public void logError (final Throwable throwable, final boolean logExceptionStack this.loggingArea.notifyError (message, throwable); else this.loggingArea.notifyError (message); + this.logToFile (message); } - /** {@inheritDoc} */ - @Override - public void logText (final String text) + private void logToFile (final String message) { - this.loggingArea.notify (text); + if (this.logWriter != null) + try + { + this.logWriter.append (message); + } + catch (final IOException ex) + { + // Ignore + } } @@ -854,6 +901,22 @@ public void updateButtonStates (final boolean canClose) loggerComponent.setAccessibleText (Functions.getMessage ("IDS_NOTIFY_FINISHED")); } + if (canClose) + { + if (this.logWriter != null) + { + try + { + this.logWriter.close (); + } + catch (final IOException ex) + { + // Ignore + } + this.logWriter = null; + } + } + }); } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index af1e47a..d13b3c3 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -13,8 +13,6 @@ requires transitive de.mossgrabers.uitools; requires javafx.graphics; requires com.github.trilarion.sound; - requires org.fxmisc.richtext; - requires org.fxmisc.flowless; exports de.mossgrabers.convertwithmoss.ui; @@ -35,4 +33,5 @@ opens de.mossgrabers.convertwithmoss.images; opens de.mossgrabers.convertwithmoss.templates.nki; opens de.mossgrabers.convertwithmoss.templates.adv; + opens de.mossgrabers.convertwithmoss.templates.ysfc; } diff --git a/src/main/resources/Strings.properties b/src/main/resources/Strings.properties index 20aae65..9a009d3 100644 --- a/src/main/resources/Strings.properties +++ b/src/main/resources/Strings.properties @@ -1,4 +1,4 @@ -TITLE=ConvertWithMoss 11.6.0 +TITLE=ConvertWithMoss 11.7.0 ################################################################################## # @@ -39,7 +39,7 @@ IDS_NOTIFY_CANCELLED=\nCancelled.\n\n IDS_NOTIFY_FINISHED=\nFinished.\n\n IDS_NOTIFY_SKIPPED=Skipped folder '%1'\n because it contains WAV file(s) which cannot processed: %2\nError: %3 IDS_NOTIFY_NO_NAME=No common name detected. Using name of the first sample.\n -IDS_NOTIFY_DETECTED_GROUPS=Detected %1 group(s).\n +IDS_NOTIFY_DETECTED_GROUPS=Created %1 group(s).\n IDS_NOTIFY_SAVE_FAILED=Could not create multi-sample: %1\n IDS_NOTIFY_FILE_NOT_FOUND=The following file could not be found: %1\n IDS_NOTIFY_COMMA=Values are comma separated. @@ -89,6 +89,8 @@ IDS_NOTIFY_ERR_MISSING_SAMPLE_DATA=\nZone '%1' does not have sample data (maybe IDS_NOTIFY_SEARCH_SAMPLE_IN=Looking for sample in '%1'...\n IDS_NOTIFY_SEARCH_SAMPLE_IN_FOUND=Found.\n IDS_NOTIFY_ERR_NO_TYPE_SELECTED=Please select at least one sample file type. +IDS_NOTIFY_FOUND_RAW_FILES=Detected %1 %2 files.\nProcessing +IDS_NOTIFY_ERR_NO_LOG_FILE=Could not create log file: %1\n IDS_NOTIFY_PROCESSING=Processing IDS_NOTIFY_FINISHED=Finished @@ -138,10 +140,9 @@ IDS_EXS_NO_SAMPLES=The file does not reference any samples but has %1 zones.\n IDS_KMP_UNKNOWN_CHUNK=Found unknown chunk: %1 IDS_KMP_WRONG_CHUNK_LENGTH=Wrong length of chunk '%1': was %2 but expected %3.\n -IDS_KMP_ERR_SKIPPED_SAMPLE=Cannot handle skipped sample.\n +IDS_KMP_SKIPPED_SAMPLE=Zone with 'skipped sample' reference found (will be removed).\n IDS_KMP_ERR_INTERNAL_SAMPLE=Cannot handle internal sample: %1\n IDS_KMP_ERR_KSF_NOT_FOUND=KSF file not found: %1\n -IDS_KMP_ERR_REFERENCED_KSF_NOT_SUPPORTED=Cross-referenced KSF files are currently not supported. Please get in touch and send me the file for analysis.\n IDS_KMP_ERR_DISTRIBUTED_KSF_NOT_SUPPORTED=Distributed KSF files are currently not supported. Please get in touch and send me the file for analysis.\n IDS_KMP_COMPRESSED_DATA_NOT_SUPPORTED=Compressed sample data is currently not supported. Please get in touch and send me the file for analysis.\n IDS_KMP_BIT_SIZE_NOT_SUPPORTED=KSF format only supports 8 or 16 bit samples but found %1.\n @@ -242,6 +243,9 @@ IDS_YSFC_DIFFERENT_NUMBER_OF_WAVEFORM_CHUNKS=Different number of waveform chunks IDS_YSFC_WAVE_FORMAT_NOT_SUPPORTED=Unsupported sample format: %1. Cannot decode the wave form(s).\n IDS_YSFC_ENCRYPTED_SAMPLES=Samples are in compressed format which is not supported.\n IDS_YSFC_MISSING_RIGHT_SAMPLE=Missing right stereo sample.\n +IDS_YSFC_UNKNOWN_PART_TYPE=Unknown part type: %1\n +IDS_YSFC_VERSION_NOT_SUPPORTED=Version '%1' not supported for performances.\n +IDS_YSFC_NO_PERFORMANCES=File does not contain performances. Multi-samples are created directly from key-banks/wave data.\n ################################################################################### # UI diff --git a/src/main/resources/de/mossgrabers/convertwithmoss/templates/ysfc/MontageDPFM.bin b/src/main/resources/de/mossgrabers/convertwithmoss/templates/ysfc/MontageDPFM.bin new file mode 100644 index 0000000..0aeeed6 Binary files /dev/null and b/src/main/resources/de/mossgrabers/convertwithmoss/templates/ysfc/MontageDPFM.bin differ