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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
/.classpath
/.project
/.DS_Store

/.idea
*.iml
*.ipr
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package de.mossgrabers.convertwithmoss.format.yamaha.ysfc;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To not pollute the main code with changes to this block, the entry for the section was subclassed.

Not an ideal solution from theoretical point of view, but works.


import de.mossgrabers.convertwithmoss.file.StreamUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class YamahaMOXFEWFMEntry extends YamahaYsfcEntry {

@Override
protected byte[] createContent() throws IOException {
final ByteArrayOutputStream contentStream = new ByteArrayOutputStream ();

StreamUtils.padBytes(contentStream, 4);

// Size of the item corresponding to this entry
StreamUtils.writeUnsigned32(contentStream, this.correspondingDataSize, true);
StreamUtils.padBytes(contentStream, 4);
// Offset of the item chunk within the data block
StreamUtils.writeUnsigned32(contentStream, this.correspondingDataOffset, true);
// Type specific - e.g. Program number
StreamUtils.writeUnsigned32(contentStream, this.specificValue, true);

StreamUtils.padBytes(contentStream, 2);

StreamUtils.writeNullTerminatedASCII (contentStream, this.itemName);
StreamUtils.writeNullTerminatedASCII (contentStream, this.itemTitle);

// Optional additional data - type specific, only used by EPFM
contentStream.write (this.additionalData);

// Finally, write the chunk
final byte [] content = contentStream.toByteArray ();
this.length = content.length;
return content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package de.mossgrabers.convertwithmoss.format.yamaha.ysfc;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: To not pollute the main code with changes to this block, the entry for the section was subclassed.

Not an ideal solution from theoretical point of view, but works.


import de.mossgrabers.convertwithmoss.file.StreamUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class YamahaMOXFEWIMEntry extends YamahaYsfcEntry {

@Override
protected byte[] createContent() throws IOException {
final ByteArrayOutputStream contentStream = new ByteArrayOutputStream ();

StreamUtils.padBytes(contentStream, 4);

// Size of the item corresponding to this entry
StreamUtils.writeUnsigned32 (contentStream, this.correspondingDataSize, true);

StreamUtils.padBytes(contentStream, 4);

// Offset of the item chunk within the data block
StreamUtils.writeUnsigned32 (contentStream, this.correspondingDataOffset, true);
// Type specific - e.g. Program number
StreamUtils.writeUnsigned32 (contentStream, this.specificValue, true);

// Flags - type specific
// contentStream.write (this.flags);

// ID of the entry object for ordering
// StreamUtils.writeUnsigned32 (contentStream, this.entryID, true);

StreamUtils.padBytes(contentStream, 2);

StreamUtils.writeNullTerminatedASCII (contentStream, this.itemName);
StreamUtils.writeNullTerminatedASCII (contentStream, this.itemTitle);

// Optional additional data - type specific, only used by EPFM
contentStream.write (this.additionalData);

// Finally, write the chunk
final byte [] content = contentStream.toByteArray ();
this.length = content.length;
return content;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.*;

import de.mossgrabers.convertwithmoss.core.IMultisampleSource;
import de.mossgrabers.convertwithmoss.core.INotifier;
Expand All @@ -35,6 +31,8 @@
import javafx.scene.control.ToggleGroup;


record MOXFData(int numberOfSamplesWritten, int numberOfChannelsWritten) {}

/**
* Creator for Yamaha YSFC files.
*
Expand All @@ -55,7 +53,8 @@ private enum OutputFormat
MONTAGE_USER,
MONTAGE_LIBRARY,
MODX_USER,
MODX_LIBRARY
MODX_LIBRARY,
MOXF_LIBRARY
}


Expand All @@ -68,11 +67,13 @@ private enum OutputFormat
ENDING_MAP.put (OutputFormat.MONTAGE_LIBRARY, ".X7L");
ENDING_MAP.put (OutputFormat.MODX_USER, ".X8U");
ENDING_MAP.put (OutputFormat.MODX_LIBRARY, ".X8L");
ENDING_MAP.put (OutputFormat.MOXF_LIBRARY, ".X3A");

VERSION_MAP.put (OutputFormat.MONTAGE_USER, "4.0.5");
VERSION_MAP.put (OutputFormat.MONTAGE_LIBRARY, "4.0.5");
VERSION_MAP.put (OutputFormat.MODX_USER, "5.0.1");
VERSION_MAP.put (OutputFormat.MODX_LIBRARY, "5.0.1");
VERSION_MAP.put (OutputFormat.MOXF_LIBRARY, "1.0.2");
}

private ToggleGroup outputFormatGroup;
Expand All @@ -97,7 +98,7 @@ public Node getEditPane ()

panel.createSeparator ("@IDS_YSFC_LIBRARY_FORMAT");
this.outputFormatGroup = new ToggleGroup ();
for (int i = 0; i < 4; i++)
for (int i = 0; i < 5; i++)
{
final RadioButton order = panel.createRadioButton ("@IDS_YSFC_OUTPUT_FORMAT_OPTION" + i);
order.setAccessibleHelp (Functions.getMessage ("IDS_YSFC_LIBRARY_FORMAT"));
Expand Down Expand Up @@ -173,11 +174,29 @@ public void create (final File destinationFolder, final List<IMultisampleSource>
*/
private static void storeMultisamples (final List<IMultisampleSource> multisampleSources, final File multiFile, final OutputFormat outputFormat) throws IOException
{
final YsfcFile ysfcFile = new YsfcFile ();
final YsfcFile ysfcFile;
if (!outputFormat.equals(OutputFormat.MOXF_LIBRARY)) {
ysfcFile = YsfcFile.withChunks ("EWFM", "DWFM", "EWIM", "DWIM");
} else {
ysfcFile = YsfcFile.withChunks ("EWFM", "DWFM", "EWIM", "DWIM", "EARP", "DARP", "EVCE", "DVCE");
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not sure if the chunks in X3A file can be skipped, so added all of them and left them empty.

}

ysfcFile.setVersionStr (VERSION_MAP.get (outputFormat));

final int libraryID = 0x10001;

Optional<MOXFData> moxfData;
if (outputFormat.equals(OutputFormat.MOXF_LIBRARY)) {
// Yamaha MOXF has following additional fields:
// - a field that for each keygroup (across all keybanks) stores total index of the samples in the library
// starting at 0x10.
// - a field that for each channel data stores its total index in DWIM block (counting channels of all
// samples in the library starting at 1.
moxfData = Optional.of(new MOXFData(16, 0));
} else {
moxfData = Optional.empty();
}

// Numbering covers all(!) samples
int sampleNumber = 1;

Expand Down Expand Up @@ -212,24 +231,36 @@ private static void storeMultisamples (final List<IMultisampleSource> multisampl
throw new IOException (ex);
}

final byte [] data = dataChunk.getData ();
final byte[] data = dataChunk.getData();
final boolean isStereo = numberOfChannels == 2;

for (int channel = 0; channel < numberOfChannels; channel++)
{
keybankList.add (createKeybank (sampleNumber, zone, formatChunk, numSamples));
keybankList.add(createKeybank(moxfData, sampleNumber, zone, formatChunk, numSamples));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please have a look at this change.

To my understanding, the keygroup in MOXF can contain stereo files.

for (int channel = 0; channel < numberOfChannels; channel++) {
final YamahaYsfcWaveData waveData = new YamahaYsfcWaveData();
waveDataList.add(waveData);
waveData.setData(isStereo ? getChannelData(channel, data) : data);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review closely as well. Not sure if this handles stereo files correctly. On my testing material, it seems to sound okay (definitely not missing a channel).

}
sampleNumber++;

final YamahaYsfcWaveData waveData = new YamahaYsfcWaveData ();
waveDataList.add (waveData);
waveData.setData (isStereo ? getChannelData (channel, data) : data);
// For MOXF:
// - calculating total count of the samples + 0x10
// - calculating total count of the channels across all samples
moxfData = moxfData.map(storage -> new MOXFData(
storage.numberOfSamplesWritten() + numSamples * numberOfChannels,
storage.numberOfChannelsWritten() + numberOfChannels)
);

sampleNumber++;
}
}

final int sampleIndex = libraryID + i;
final YamahaYsfcEntry keyBankEntry = new YamahaYsfcEntry ();
keyBankEntry.setSpecificValue (sampleIndex);
final YamahaYsfcEntry keyBankEntry;
if (!outputFormat.equals(OutputFormat.MOXF_LIBRARY)) {
keyBankEntry = new YamahaYsfcEntry ();
keyBankEntry.setSpecificValue (sampleIndex);
} else {
keyBankEntry = new YamahaMOXFEWFMEntry();
keyBankEntry.setSpecificValue (sampleIndex - libraryID + 1);
}

// Set the category
String n = multisampleName;
Expand All @@ -241,10 +272,22 @@ private static void storeMultisamples (final List<IMultisampleSource> multisampl
n = categoryID.toString () + ":" + n;
}
keyBankEntry.setItemName (n);
if (outputFormat.equals(OutputFormat.MOXF_LIBRARY)) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Item titles need to be present for MOXF.

keyBankEntry.setItemTitle(String.format("%04d-Waveform.wfm", i + 1));
}

final YamahaYsfcEntry waveDataEntry = new YamahaYsfcEntry ();

final YamahaYsfcEntry waveDataEntry;
if (!outputFormat.equals(OutputFormat.MOXF_LIBRARY)) {
waveDataEntry = new YamahaYsfcEntry ();
waveDataEntry.setSpecificValue (sampleIndex);
} else {
waveDataEntry = new YamahaMOXFEWIMEntry();
waveDataEntry.setSpecificValue (sampleIndex - libraryID + 1);
waveDataEntry.setItemTitle(String.format("%04d-Waveform.wim", i + 1));
}
waveDataEntry.setItemName (multisampleName);
waveDataEntry.setSpecificValue (sampleIndex);


ysfcFile.fillWaveChunks (keyBankEntry, keybankList, waveDataEntry, waveDataList);
}
Expand All @@ -256,11 +299,16 @@ private static void storeMultisamples (final List<IMultisampleSource> multisampl
}


private static YamahaYsfcKeybank createKeybank (final int sampleNumber, final ISampleZone zone, final FormatChunk formatChunk, final int numSamples)
private static YamahaYsfcKeybank createKeybank (Optional<MOXFData> moxfData, int sampleNumber, final ISampleZone zone, final FormatChunk formatChunk, final int numSamples)
{
final YamahaYsfcKeybank keybank = new YamahaYsfcKeybank ();
keybank.setNumber (sampleNumber);

moxfData.ifPresent(data -> {
keybank.setVersion1TotalSampleOffset(data.numberOfSamplesWritten());
keybank.setVersion1TotalChannelOffset(data.numberOfChannelsWritten());
});

final int numberOfChannels = formatChunk.getNumberOfChannels ();
keybank.setChannels (numberOfChannels);
keybank.setSampleFrequency (formatChunk.getSampleRate ());
Expand All @@ -280,11 +328,11 @@ private static YamahaYsfcKeybank createKeybank (final int sampleNumber, final IS
final double gain = zone.getGain ();
keybank.setLevel (gain < -95.25 ? 0 : (int) Math.round ((Math.clamp (gain, -95.25, 0) + 95.25) / 0.375) + 1);

if (numberOfChannels == 1)
{
final double panorama = zone.getPanorama ();
keybank.setPanorama ((int) (panorama < 0 ? panorama * 64 : panorama * 63));
}
// if (numberOfChannels == 1)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review closely. I hope this should work for stereo samples as well. Hopefully for Montage as well.

// {
final double panorama = zone.getPanorama ();
keybank.setPanorama ((int) (panorama < 0 ? panorama * 64 : panorama * 63));
// }

keybank.setPlayStart (zone.getStart ());
keybank.setPlayEnd (zone.getStop ());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
*/
public class YamahaYsfcEntry
{
private int length;
private byte [] flags = new byte [6];
private String itemName = "";
private String itemTitle = "";
private byte [] additionalData = new byte [0];
private int correspondingDataSize = 0;
private int correspondingDataOffset = 0;
private int specificValue = 0;
private int entryID = 0xFFFFFFFF;
protected int length;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the fields to protected in order to subclass this class for MOXF-specific entries.

The other approach would be making this entry class aware if it is for MOXF or not. Subclassing seemed to introduce less changes to the code.

private byte [] flags = new byte [6];
protected String itemName = "";
protected String itemTitle = "";
protected byte [] additionalData = new byte [0];
protected int correspondingDataSize = 0;
protected int correspondingDataOffset = 0;
protected int specificValue = 0;
protected int entryID = 0xFFFFFFFF;


/**
Expand Down Expand Up @@ -111,7 +111,7 @@ public void write (final OutputStream out) throws IOException
}


private byte [] createContent () throws IOException
protected byte [] createContent () throws IOException
{
final ByteArrayOutputStream contentStream = new ByteArrayOutputStream ();

Expand Down Expand Up @@ -229,6 +229,16 @@ public String getItemTitle ()
return this.itemTitle;
}

/**
* Set the title of the item. Used by Yamaha MOXF
*
* @param itemTitle The filename
*/
public void setItemTitle (final String itemTitle)
{
this.itemTitle = itemTitle;
}


/**
* Get the flags.
Expand Down
Loading