Skip to content

Commit b48603a

Browse files
committed
* 1010music format
* New: Can be a destination format for Performances (see the manual for details). * New: Stereo-split samples are now combined (if possible) to stereo samples since the format does not support panning on a sample level. * Kontakt * New: Kontakt 4.2-7: Pitchmodulation by Pitchbend and Amplitudemodulation by Velocity are now read.
1 parent 40cfff3 commit b48603a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1186
-430
lines changed

documentation/CHANGELOG.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
# Changes
22

3-
## 13.0.0 (unreleased)
3+
## 13.0.0
44

55
* New: Rearranged the destination area of the user interface. There is now a new section which allows to switch between creating single patches, libraries containing multiple patches and performances which contain a certain configuration of patches (e.g. different MIDI channels). Output formats are filtered to the ones which support these options.
66
* New: Improved maximum size of RIFF files that can be written.
77
* Fixed: Envelope could be wrong if the input envelope uses the hold-time instead of decay-time and the output format does not support a hold-time.
8+
* Fixed: The logging does now always scroll fully to the end when the conversion or analysis process has finished.
9+
* 1010music format
10+
* New: Can be a destination format for Performances (see the manual for details).
11+
* New: Stereo-split samples are now combined (if possible) to stereo samples since the format does not support panning on a sample level.
812
* Kontakt
9-
* New: Kontakt can be an input format for Performances.
13+
* New: Kontakt can be an input format for Performances (see the manual for details).
14+
* New: Kontakt 4.2-7: Pitchmodulation by Pitchbend and Amplitudemodulation by Velocity are now read.
1015
* Fixed: Fixed crash with reading envelopes from NI-container.
1116
* Decent Sampler
1217
* New: Write: There are now templates for the UI and effect sections which can be modified as well as further resources can be added automatically. See the manual for more info.
@@ -20,8 +25,6 @@
2025
* New: The long name stored in the KMP is now set as the multi-samples name instead of the short filename.
2126
* SF2
2227
* New: Added support to write as library (adds all found source-multi-samples into 1 sf2 file).
23-
* Yamaha YSFC
24-
* New: Can be a destination format for Performances.
2528

2629
## 12.2.2
2730

documentation/README-FORMATS.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ If the format is selected as the source, there are two things to consider:
6666
* Option to set the *Interpolation Quality*. Setting it to *High* requires a bit more processing power on the 1010music devices.
6767
* Option to trim sample to range of zone start to end. Since the format does not support a sample start attribute for multi-sample, this fixes the issue.
6868

69+
If 'Performance' is selected as the destination type, some workarounds are applied:
70+
71+
* MIDI channel: There is no explicit OMNI setting. Instead, if MIDI channel 1 is selected on the device it acts as the OMNI channel, which means it is always sounding and renders MIDI channel 1 to be unusable. As a solution all MIDI channels are increased by 1 (channel 1 is 2, channel 2 is 3, ...) and channel 16 is set to Off. All instruments which have OMNI configured are set to channel 1.
72+
* Key ranges: The 1010music devices do not support key-ranges which means a multi-sample is always sounding across the full note range. As a workaround a silent sample (a totally empty one-shot sample is used) is applied to the lower and upper range which should not sound.
73+
6974
## Ableton Sampler
7075

7176
Ableton uses a generic preset format (*.adv) for all of their devices. For combined rack presets another format (*.adg) is used. All their formats are XML documents which are compressed with the open GZIP algorithm.
@@ -155,7 +160,9 @@ A NKM file contains up to 64 instruments and is supported as well as a source.
155160

156161
Encrypted files are not supported.
157162

158-
If selected as a destination, a NKI file is written and all samples are placed in a sub-folder with the same name.
163+
If selected as a destination, a NKI file is written and all samples are placed in a sub-folder with the same name.
164+
165+
If selected as a source and 'Performance' is selected as the destination type, only NKM files are used as sources.
159166

160167
### Destination Options
161168

@@ -320,11 +327,9 @@ Currently, the user and library formats of the Montage (not Montage M!) and MODX
320327
* A performance can contain up to 16 parts (e.g. to perform a song by muting, soloing parts via scenes)
321328
* A library contains several performances
322329

323-
When creating presets or libraries as the destination type, each multi-sample source creates one performance with one active part. Each group of the the multi-sample source is assigned to 1 element for which 1 key-group is created as well which contains the samples. If there are more groups than elements, the remaining groups are all added to the last element. If there are no groups all samples will be assigned to key-group/element one.
324-
325-
When creating (ConvertWithMoss) performances as the destination type, each performance source creates one (Yamaha) performance with one active part for each multi-sample (instrument) of the source. If there are more than 16 instruments in the source they are ignored. The rest of the mapping is identical to creating presets/libraries.
330+
**Destination Type: Preset or Library**
326331

327-
Note: There are no checks that the created libraries stay in the boundaries of the workstation specifications (e.g. the number of the maximum allowed samples or the required memory size)!
332+
When creating presets or libraries as the destination type, each multi-sample source creates one performance with one active part. Each group of the the multi-sample source is assigned to 1 element for which 1 key-group is created as well which contains the samples. If there are more groups than elements, the remaining groups are all added to the last element. If there are no groups all samples will be assigned to key-group/element one.
328333

329334
### Source Options
330335

15.1 KB
Binary file not shown.

src/main/java/de/mossgrabers/convertwithmoss/core/IInstrumentSource.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,29 @@ public interface IInstrumentSource extends ISource
2424
* Get the MIDI channel of the instrument.
2525
*
2626
* @return The MIDI channel in the range of [0..15], -1 and all other values are considered
27-
* omni/all
27+
* OMNI/all
2828
*/
2929
int getMidiChannel ();
3030

31-
// TODO Do we need a key-range as well?
31+
32+
/**
33+
* The lower note which should limit the key-range (this note should still sound).
34+
*
35+
* @return The note [0..127]
36+
*/
37+
int getClipKeyLow ();
38+
39+
40+
/**
41+
* The upper note which should limit the key-range (this note should still sound).
42+
*
43+
* @return The note [0..127]
44+
*/
45+
int getClipKeyHigh ();
46+
47+
48+
/**
49+
* Clip all samples (or remove them fully) if there are outside of the lower and upper key.
50+
*/
51+
void clipKeyRange ();
3252
}

src/main/java/de/mossgrabers/convertwithmoss/core/MathUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ public static long calcCRC32 (final byte [] data)
203203

204204

205205
/**
206-
* Converts a time in seconds into a normalized logarithmic value in the range [0..1].
206+
* Converts a time in seconds into a normalized logarithmic value in the range [0..1000].
207207
*
208208
* @param value The value in seconds
209209
* @param maxValue The maximum for value
@@ -216,7 +216,7 @@ public static String normalizeTimeFormattedAsInt (final double value, final doub
216216

217217

218218
/**
219-
* Converts a time in seconds into a normalized logarithmic value in the range [0..1],
219+
* Converts a time in seconds into a normalized logarithmic value in the range [0..1000],
220220
* multiplies it with 1000 and converts it to an integer.
221221
*
222222
* @param value The value in seconds

src/main/java/de/mossgrabers/convertwithmoss/core/ZoneChannels.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import de.mossgrabers.convertwithmoss.core.model.IAudioMetadata;
1818
import de.mossgrabers.convertwithmoss.core.model.IGroup;
19+
import de.mossgrabers.convertwithmoss.core.model.ISampleData;
1920
import de.mossgrabers.convertwithmoss.core.model.ISampleLoop;
2021
import de.mossgrabers.convertwithmoss.core.model.ISampleZone;
2122
import de.mossgrabers.convertwithmoss.core.model.implementation.DefaultGroup;
@@ -59,7 +60,10 @@ public static ZoneChannels detectChannelConfiguration (final List<IGroup> groups
5960
for (final IGroup group: groups)
6061
for (final ISampleZone sampleZone: group.getSampleZones ())
6162
{
62-
final boolean isStereo = sampleZone.getSampleData ().getAudioMetadata ().getChannels () == 2;
63+
final ISampleData sampleData = sampleZone.getSampleData ();
64+
if (sampleData == null)
65+
continue;
66+
final boolean isStereo = sampleData.getAudioMetadata ().getChannels () == 2;
6367
if (stereo == null)
6468
{
6569
// First iteration, store if mono or stereo

src/main/java/de/mossgrabers/convertwithmoss/core/creator/AbstractCreator.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.nio.file.attribute.FileTime;
1616
import java.util.ArrayList;
1717
import java.util.Collection;
18+
import java.util.Collections;
1819
import java.util.Date;
1920
import java.util.HashSet;
2021
import java.util.List;
@@ -37,6 +38,7 @@
3738
import de.mossgrabers.convertwithmoss.core.IMultisampleSource;
3839
import de.mossgrabers.convertwithmoss.core.INotifier;
3940
import de.mossgrabers.convertwithmoss.core.IPerformanceSource;
41+
import de.mossgrabers.convertwithmoss.core.ZoneChannels;
4042
import de.mossgrabers.convertwithmoss.core.model.IGroup;
4143
import de.mossgrabers.convertwithmoss.core.model.IMetadata;
4244
import de.mossgrabers.convertwithmoss.core.model.ISampleData;
@@ -590,6 +592,33 @@ protected List<File> writeSamples (final File sampleFolder, final IMultisampleSo
590592
}
591593

592594

595+
/**
596+
* Since panning is not working on the sample level, combine split stereo to stereo files If the
597+
* combination fails, the file is created anyway but might contain wrong panning.
598+
*
599+
* @param multisampleSource The multi-sample source
600+
* @return The combined stereo group or the original groups if they could not be combined
601+
* @throws IOException Could not combine the groups
602+
*/
603+
protected List<IGroup> combineSplitStereo (final IMultisampleSource multisampleSource) throws IOException
604+
{
605+
final List<IGroup> groups = multisampleSource.getNonEmptyGroups (true);
606+
607+
if (ZoneChannels.detectChannelConfiguration (groups) != ZoneChannels.SPLIT_STEREO)
608+
return groups;
609+
610+
final Optional<IGroup> stereoGroup = ZoneChannels.combineSplitStereo (groups);
611+
if (stereoGroup.isPresent ())
612+
{
613+
this.notifier.log ("IDS_NOTIFY_COMBINED_TO_STEREO");
614+
return Collections.singletonList (stereoGroup.get ());
615+
}
616+
617+
this.notifier.logError ("IDS_NOTIFY_NOT_COMBINED_TO_STEREO");
618+
return groups;
619+
}
620+
621+
593622
/**
594623
* Create the name of the sample file.
595624
*

src/main/java/de/mossgrabers/convertwithmoss/core/detector/AbstractDetector.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ public void cancel ()
7878
protected void startDetection (final T detectorTask)
7979
{
8080
this.detectorTaskOptional = Optional.of (detectorTask);
81-
detectorTask.setOnCancelled (event -> this.notifier.updateButtonStates (true));
82-
detectorTask.setOnFailed (event -> this.notifier.updateButtonStates (true));
83-
detectorTask.setOnSucceeded (event -> this.notifier.updateButtonStates (true));
84-
detectorTask.setOnRunning (event -> this.notifier.updateButtonStates (false));
85-
detectorTask.setOnScheduled (event -> this.notifier.updateButtonStates (false));
81+
detectorTask.setOnCancelled (_ -> this.notifier.updateButtonStates (true));
82+
detectorTask.setOnFailed (_ -> this.notifier.updateButtonStates (true));
83+
detectorTask.setOnSucceeded (_ -> this.notifier.updateButtonStates (true));
84+
detectorTask.setOnRunning (_ -> this.notifier.updateButtonStates (false));
85+
detectorTask.setOnScheduled (_ -> this.notifier.updateButtonStates (false));
8686
this.executor.execute (detectorTask);
8787
}
8888

src/main/java/de/mossgrabers/convertwithmoss/core/detector/AbstractDetectorTask.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ protected void checkChildTags (final String tagName, final Set<String> supported
298298
{
299299
final String nodeName = childElement.getNodeName ();
300300
if (!supportedElements.contains (nodeName))
301-
this.unsupportedElements.computeIfAbsent (tagName, tag -> new HashSet<> ()).add (nodeName);
301+
this.unsupportedElements.computeIfAbsent (tagName, _ -> new HashSet<> ()).add (nodeName);
302302
}
303303
}
304304

@@ -339,7 +339,7 @@ protected void checkAttributes (final String tagName, final NamedNodeMap attribu
339339
{
340340
final String nodeName = attributes.item (i).getNodeName ();
341341
if (!supportedAttributes.contains (nodeName))
342-
this.unsupportedAttributes.computeIfAbsent (tagName, tag -> new HashSet<> ()).add (nodeName);
342+
this.unsupportedAttributes.computeIfAbsent (tagName, _ -> new HashSet<> ()).add (nodeName);
343343
}
344344
}
345345

src/main/java/de/mossgrabers/convertwithmoss/core/detector/DefaultInstrumentSource.java

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44

55
package de.mossgrabers.convertwithmoss.core.detector;
66

7+
import java.util.ArrayList;
8+
import java.util.List;
9+
710
import de.mossgrabers.convertwithmoss.core.IInstrumentSource;
811
import de.mossgrabers.convertwithmoss.core.IMultisampleSource;
12+
import de.mossgrabers.convertwithmoss.core.model.IGroup;
13+
import de.mossgrabers.convertwithmoss.core.model.ISampleZone;
914

1015

1116
/**
@@ -15,8 +20,10 @@
1520
*/
1621
public class DefaultInstrumentSource extends DefaultSource implements IInstrumentSource
1722
{
18-
private final IMultisampleSource multisampleSource;
23+
private IMultisampleSource multisampleSource;
1924
private int midiChannel;
25+
private int clipKeyLow = 0;
26+
private int clipKeyHigh = 127;
2027

2128

2229
/**
@@ -41,6 +48,17 @@ public IMultisampleSource getMultisampleSource ()
4148
}
4249

4350

51+
/**
52+
* Set the multi-sample source.
53+
*
54+
* @param multisampleSource The source
55+
*/
56+
public void setMultisampleSource (final IMultisampleSource multisampleSource)
57+
{
58+
this.multisampleSource = multisampleSource;
59+
}
60+
61+
4462
/** {@inheritDoc} */
4563
@Override
4664
public int getMidiChannel ()
@@ -59,4 +77,73 @@ public void setMidiChannel (final int midiChannel)
5977
{
6078
this.midiChannel = midiChannel;
6179
}
80+
81+
82+
/** {@inheritDoc} */
83+
@Override
84+
public int getClipKeyLow ()
85+
{
86+
return this.clipKeyLow;
87+
}
88+
89+
90+
/**
91+
* Set the lower note which should limit the key-range (this note should still sound).
92+
*
93+
* @param clipKeyLow The note [0..127]
94+
*/
95+
public void setClipKeyLow (final int clipKeyLow)
96+
{
97+
this.clipKeyLow = clipKeyLow;
98+
}
99+
100+
101+
/** {@inheritDoc} */
102+
@Override
103+
public int getClipKeyHigh ()
104+
{
105+
return this.clipKeyHigh;
106+
}
107+
108+
109+
/**
110+
* Set the upper note which should limit the key-range (this note should still sound).
111+
*
112+
* @param clipKeyHigh The note [0..127]
113+
*/
114+
public void setClipKeyHigh (final int clipKeyHigh)
115+
{
116+
this.clipKeyHigh = clipKeyHigh;
117+
}
118+
119+
120+
/** {@inheritDoc} */
121+
@Override
122+
public void clipKeyRange ()
123+
{
124+
// Nothing to do?
125+
if (this.clipKeyLow == 0 && this.clipKeyHigh == 127)
126+
return;
127+
128+
for (final IGroup group: this.multisampleSource.getGroups ())
129+
{
130+
final List<ISampleZone> filteredZones = new ArrayList<> ();
131+
for (final ISampleZone zone: group.getSampleZones ())
132+
{
133+
int keyLow = zone.getKeyLow ();
134+
int keyHigh = zone.getKeyHigh ();
135+
// Fully outside -> remove
136+
if (keyLow > this.clipKeyHigh || keyHigh < this.clipKeyLow)
137+
continue;
138+
139+
// Clip lower and upper range
140+
if (keyLow < this.clipKeyLow)
141+
zone.setKeyLow (this.clipKeyLow);
142+
if (keyHigh > this.clipKeyHigh)
143+
zone.setKeyHigh (this.clipKeyHigh);
144+
filteredZones.add (zone);
145+
}
146+
group.setSampleZones (filteredZones);
147+
}
148+
}
62149
}

0 commit comments

Comments
 (0)