Skip to content

AIFF: files with 32-bit floating-point sound data (AIFC 'fl32') are read - #484

Closed
douglas-carmichael wants to merge 818 commits into
git-moss:mainfrom
douglas-carmichael:fix-aiff-float
Closed

douglas-carmichael wants to merge 818 commits into
git-moss:mainfrom
douglas-carmichael:fix-aiff-float

Conversation

@douglas-carmichael

Copy link
Copy Markdown
Contributor

AIFC files whose sound data is 32-bit floating-point (compression type fl32) were rejected as if they were compressed: AiffCommonChunk.isPCM accepted only the integer types (NONE, twos, sowt, in24, in32, 23ni), so such a file failed with 'Source format not supported (or the file might be corrupted)'. Live's own content uses the format:

  • 'Clave Synth' of the Core Library of Ableton Live 12. It is the only float file among the 1,351 AIFF samples of the library, but the rack 'Percussion Spirit Kit' plays it, so the whole kit could not be read: 'Could not load file: Source format not supported (or the file might be corrupted): Clave Synth.aif'.
  • The spring impulse response 'Moist Spring Mono' of Ableton's 'Convolution Reverb' and 'Max for Live Essentials' packs.

The sound data of fl32 is plain big-endian IEEE float. AiffCommonChunk.isFloatingPoint recognizes it (fl32 or FL32 with a sample size of 32 bits) and isPCM accepts it. AiffFileSampleData converts it from the chunks like the other AIFC files, which the Java sound system cannot read: the bytes are swapped to little-endian and the WAV format chunk is marked as IEEE float, as CafFileSampleData and NcwFile already do for their float data.

Checked:

  • 'Clave Synth' (mono, 48 kHz) and 'Moist Spring Mono' (stereo, 44.1 kHz) convert to float WAV files which are identical, sample for sample, to the source data and to the WAV files which afconvert -d LEF32 writes from them. afinfo reads them as Float32.
  • 'Percussion Spirit Kit' delivers all its 16 multi-samples instead of failing.
  • With the processing options set to 16 bit and 44.1 kHz, the 48 kHz float sample becomes a 16-bit sample of the expected length.
  • Converting the multi-samples of the Core Library (221 sowt AIFC files and 203 AIFF files, 16 and 24 bit) with the Sample Files source writes the same 29 SFZ files and 325 samples as before, byte for byte, with the same log.
  • Compressed AIFC files are still rejected, e.g. the samples of the packs which Live installs from Ableton (compression type able).

CHANGELOG and README-FORMATS (Sample files) are updated.

git-moss and others added 30 commits July 30, 2026 14:42
…ed on git-moss#238) (git-moss#241)

* Convert only specific files instead of a whole source folder

A folder often contains many files of the source format of which only
some should be converted, e.g. a couple of EXS programs or disk images.
Until now the whole folder had to be converted.

* User interface: next to the source *Folder* button there is now a
  *Files* button, which opens a multi-selection file dialog filtered by
  the file endings of the selected source format. The path field keeps
  showing the folder, the selection is displayed below it and can be
  discarded with *Clear*. Changing the source path discards it as well,
  so the displayed path never contradicts what is converted. Entering
  the path of a file converts that one file.
* Command line: one or more source files can be given instead of the
  source folder, which is most useful with a wildcard. The last given
  path stays the destination folder, therefore the previous
  SOURCE_FOLDER DESTINATION_FOLDER form is unchanged.

A selection only replaces the file search, it is not a new grouping
mode: each file is converted on its own, exactly like it would be as
part of a full folder conversion, and the destination type still
decides whether the results become single presets or one library. The
Sample Files source keeps combining the files (its contract is that all
sample files of a folder form one multi-sample), the selection just
states which of them take part.

The source folder therefore stays a real folder in the detectors - it
is the parent folder of the files - since it is the reference for all
sub-path calculations. Verified that a converted selection is
byte-identical to the files of a full folder conversion.

* Select individual presets of a bank or a disk image to convert

One source file can contain far more presets than the ones which should
be converted: a CD-ROM image of an E-mu sampler holds thousands of them
in dozens of banks, and the same is true for the disk images of the
Akai, Ensoniq and Roland samplers, for SoundFont 2 banks and for the
libraries of several plug-ins.

A source format where one file holds several presets now has a
*Contents...* button. It detects everything in the source without
writing anything and shows the result as a tree of the file and of the
containers inside of it - e.g. image, bank, preset. Each entry shows
its number of zones, its key range and its category, so that a bank
which is not known yet can be explored. Only the ticked presets are
converted; how many of them are selected is shown below the source path
and can be discarded again.

The selection is applied where the detectors deliver their sources,
therefore it works for every format without any format specific code.
A source is identified by its index inside of its file, which is
deterministic and addresses the same source in the conversion run;
names cannot be used since a bank may contain several presets with the
same name. Since the index is counted per file, the files which contain
no selected preset are not even opened.

Selecting the files to read beforehand keeps the detection cheap: for
two E-mu CD-ROM images (1.1 GB, 2339 presets) reading only the one
image which is of interest takes the contents run from 36 to 10
seconds.

* Replace the file selection with a Batch toggle

Reworked after the review: the source keeps its original layout with one
Select button, and a Batch toggle sits next to it.

Batch is on by default and everything works as before - Select picks the
source folder and every file of the source format in it is converted.
Switching it off converts one single file: Select then opens a file
dialog whose filter box lists every source format with its file endings,
the current one preselected, and the source format is set afterwards from
what was picked. A filter which was switched in the dialog wins, otherwise
the file ending decides; the endings of disk images (ISO, IMG, HDA) go to
the generic ISO/IMG format, which hands the image to whichever sampler
format can read it, since the E-mu formats claim the same endings.

Each mode keeps a path history of its own. Toggling Batch exchanges the
two histories and puts the last path of the mode which is entered into the
text field, and both are stored in the configuration.

This drops the multi-file selection, its status line below the source path
and the Clear button, so the extra row is gone from the layout again. A
single file needs no verification of a stored selection either, which
removes that code and its two messages - entering a file path by hand was
and stays the same thing as picking one.

* Restore the message of the contents detection run

Reworking the branch onto the Batch design took the string file from its
new base and added the contents strings back one by one, which missed
IDS_NOTIFY_DETECTING_CONTENTS - it is used by the backend and not by the
dialog, so checking the two user interface classes did not catch it.

A missing key makes the message lookup fail inside the run, so pressing
Contents on a source which the format cannot read left the execution
window open with nothing in it: no progress, no result and no way back
except Close. With the string in place the run reports what it is doing,
finishes, and an empty result reports 'No presets were found in the
source.' as it was meant to.

* Describe the Batch toggle in the user manual

The manual still told the reader to use a *Folder* and a *Files* button
and to hold Shift to pick several files. That was the first design of
this branch; replacing it with the Batch toggle left the text behind, so
it describes two buttons which are not in the window and a multiple
selection which no longer exists.

* Move the changelog entry to the collected changelog PR git-moss#283

The open PRs all inserted their changelog entries near the same lines,
so every merged PR turned the remaining ones into conflicts. The
entries of the whole batch are collected in PR git-moss#283 and quoted in each
PR's description; the branches no longer touch the changelog, so they
merge cleanly in any order.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…s#241) (git-moss#250)

* Convert only specific files instead of a whole source folder

A folder often contains many files of the source format of which only
some should be converted, e.g. a couple of EXS programs or disk images.
Until now the whole folder had to be converted.

* User interface: next to the source *Folder* button there is now a
  *Files* button, which opens a multi-selection file dialog filtered by
  the file endings of the selected source format. The path field keeps
  showing the folder, the selection is displayed below it and can be
  discarded with *Clear*. Changing the source path discards it as well,
  so the displayed path never contradicts what is converted. Entering
  the path of a file converts that one file.
* Command line: one or more source files can be given instead of the
  source folder, which is most useful with a wildcard. The last given
  path stays the destination folder, therefore the previous
  SOURCE_FOLDER DESTINATION_FOLDER form is unchanged.

A selection only replaces the file search, it is not a new grouping
mode: each file is converted on its own, exactly like it would be as
part of a full folder conversion, and the destination type still
decides whether the results become single presets or one library. The
Sample Files source keeps combining the files (its contract is that all
sample files of a folder form one multi-sample), the selection just
states which of them take part.

The source folder therefore stays a real folder in the detectors - it
is the parent folder of the files - since it is the reference for all
sub-path calculations. Verified that a converted selection is
byte-identical to the files of a full folder conversion.

* Select individual presets of a bank or a disk image to convert

One source file can contain far more presets than the ones which should
be converted: a CD-ROM image of an E-mu sampler holds thousands of them
in dozens of banks, and the same is true for the disk images of the
Akai, Ensoniq and Roland samplers, for SoundFont 2 banks and for the
libraries of several plug-ins.

A source format where one file holds several presets now has a
*Contents...* button. It detects everything in the source without
writing anything and shows the result as a tree of the file and of the
containers inside of it - e.g. image, bank, preset. Each entry shows
its number of zones, its key range and its category, so that a bank
which is not known yet can be explored. Only the ticked presets are
converted; how many of them are selected is shown below the source path
and can be discarded again.

The selection is applied where the detectors deliver their sources,
therefore it works for every format without any format specific code.
A source is identified by its index inside of its file, which is
deterministic and addresses the same source in the conversion run;
names cannot be used since a bank may contain several presets with the
same name. Since the index is counted per file, the files which contain
no selected preset are not even opened.

Selecting the files to read beforehand keeps the detection cheap: for
two E-mu CD-ROM images (1.1 GB, 2339 presets) reading only the one
image which is of interest takes the contents run from 36 to 10
seconds.

* Replace the file selection with a Batch toggle

Reworked after the review: the source keeps its original layout with one
Select button, and a Batch toggle sits next to it.

Batch is on by default and everything works as before - Select picks the
source folder and every file of the source format in it is converted.
Switching it off converts one single file: Select then opens a file
dialog whose filter box lists every source format with its file endings,
the current one preselected, and the source format is set afterwards from
what was picked. A filter which was switched in the dialog wins, otherwise
the file ending decides; the endings of disk images (ISO, IMG, HDA) go to
the generic ISO/IMG format, which hands the image to whichever sampler
format can read it, since the E-mu formats claim the same endings.

Each mode keeps a path history of its own. Toggling Batch exchanges the
two histories and puts the last path of the mode which is entered into the
text field, and both are stored in the configuration.

This drops the multi-file selection, its status line below the source path
and the Clear button, so the extra row is gone from the layout again. A
single file needs no verification of a stored selection either, which
removes that code and its two messages - entering a file path by hand was
and stays the same thing as picking one.

* Restore the message of the contents detection run

Reworking the branch onto the Batch design took the string file from its
new base and added the contents strings back one by one, which missed
IDS_NOTIFY_DETECTING_CONTENTS - it is used by the backend and not by the
dialog, so checking the two user interface classes did not catch it.

A missing key makes the message lookup fail inside the run, so pressing
Contents on a source which the format cannot read left the execution
window open with nothing in it: no progress, no result and no way back
except Close. With the string in place the run reports what it is doing,
finishes, and an empty result reports 'No presets were found in the
source.' as it was meant to.

* Render and play one note of a multi-sample

The engine behind auditioning a preset before it is converted. It renders
what the MODEL holds, not what the source file holds, which is the point:
a preset which converts to silence or which lost its envelope sounds
exactly as wrong here as it will on the device.

Every zone which the played note triggers is mixed, so a layered preset
is heard as it is layered. Each zone is pitched by its distance to its
root key scaled by its key tracking, its loop sustains the note, and the
amplitude envelope, the filter with its own envelope, key tracking and
velocity, the pitch and volume LFOs, the velocity to amplitude
modulation, the gain and the panning shape it. The filter is a
topology-preserving state variable, one stage per pole pair, so it stays
stable while the envelope moves the cutoff.

Reading the audio goes through ISampleData.writeSample, which every
format implements, so this works for all of them without a single line
of per-format code.

Measured on 'Producer Series Vol. 5 - 3-D Audio Collection': all 229
presets render audibly, none silent, 8.2 ms each. 'VFX Miami Pad' stops
after 2 seconds, which is the decay to zero sustain its EIII envelope
asks for, and '3DPads - 3DreamSwells' - which converted to silence until
git-moss#245 - now renders at full level.

* Listen to a preset of a bank before converting it

The contents dialog now plays one note of the highlighted preset, with
the Play button or a double-click. That closes the gap the dialog left
open: it lists what is inside a bank, but a name like 'Pad 3' or 'Trom /
Sax' does not tell whether it is worth the storage. Picking by ear is
what the dialog is for.

This matters most where it is expensive to be wrong. A hardware
destination has little room - and on a device like the Waldorf Iridium,
removing samples again afterwards is a chore. Converting a whole E-mu
CD-ROM to find the three usable presets costs an afternoon; hearing them
first costs a tenth of a second each.

The entries hold only the information to display, since keeping every
source of a disk image in memory is exactly what they were designed to
avoid, so the one preset is read again on demand. IDetector.readSource
does that: it runs the same detectFiles path as a conversion, counts the
index the same way the detection run counts it, stops as soon as the
wanted source arrives and returns it on the calling thread. The dialog
calls it from a background thread and the main frame keeps the last read
source, so playing the same preset again is instant.

Detector messages are suppressed for that read. The log window is not
visible while the dialog is, so every message would otherwise open a
message dialog - errors still go to the log file.

Verified against the real detectors, comparing the name of each re-read
source with the name the contents run reported: 229 of 229 presets of
'Producer Series Vol. 5', 755 of 755 of the EIIIX 'Dance 2000' CD-ROM
and 962 of 2885 sampled across two E4 images read through the generic
ISO/IMG format - no mismatches, none unreadable, none silent. Reading
takes 66 to 150 ms per preset inside those images, rendering 8 ms.
SoundFont 2 and DecentSampler libraries were checked as well, since the
audio comes from ISampleData and no format is special-cased.

* Describe the Batch toggle in the user manual

The manual still told the reader to use a *Folder* and a *Files* button
and to hold Shift to pick several files. That was the first design of
this branch; replacing it with the Batch toggle left the text behind, so
it describes two buttons which are not in the window and a multiple
selection which no longer exists.

* Move the changelog entry to the collected changelog PR git-moss#283

The open PRs all inserted their changelog entries near the same lines,
so every merged PR turned the remaining ones into conflicts. The
entries of the whole batch are collected in PR git-moss#283 and quoted in each
PR's description; the branches no longer touch the changelog, so they
merge cleanly in any order.

* Audition: never end a note with a click and keep the Play button off the tree border

A preset whose amplitude envelope has no release was cut off from one
frame to the next at the note-off, which clicks - on the Analogia
library 188 of 271 presets ended on a step of more than 5% of their
peak, the worst at 93%. Such a release now ramps down over the fade
time instead. The same happened where the audio itself runs out: a
zone which does not loop, or whose loop ended with the release, simply
stopped at its end point in the middle of the wave; it is faded out
now as well. The rows above and below the preset tree of the contents
dialog also got a small vertical padding, since the Play button sat
directly on the border of the tree.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…t of the source text field

- Enabled S-550 CD-ROMs for generic ISO detector
- Consolidation
- Removed setting pitch which adds to the other values
- Always set root note on layer, since it cannot be ensured to be present in the WAV SMPL chunk
- Set VelocityToAttack, filterVelocity, filterEnvelopeVelocity and filterKeytrack to 0 in Template, which had the wrong default value when it had 0 in the source and therefore was not set.
- Cutoff 24000ct is Max. Same for Key Modulation (resulting values were too high). Also fixed in XPM reading/writing
- Set track volume to 0dB (volume = 0.7079457640647888) in the template
Tx16:
- CutoffKeyTracking adjusted to 24000ct maximum (was only 1200ct)
… zone sample index of the Emulator III, and deleted-slot references (git-moss#289)

* E-mu EIII: fix stereo channel order, the EIII zone sample index and deleted-slot reports

- The two channels of a stereo sample are stored one after the other in either
  order; each channel is now read from its own start position. 'Vol. 1 -
  Emulator Standards' holds 16 right-channel-first samples which were read at
  double length with mixed-up channels, or rejected as malformed where that
  overran the bank.
- The original Emulator III stores the sample number of a zone in a single
  byte; the byte behind it holds unrelated values in many banks of the library
  CD-ROMs and is no longer read as part of the index.
- A zone which references an empty (deleted) sample slot is silent on the
  device as well; it is now reported as a plain note instead of an error.

* E-mu EIII: report the empty disk-label bank as the name of the CD-ROM instead of an error

* Remove the E-mu EIII CHANGELOG entries - the format is not released yet
…git-moss#291)

An editable JavaFX combo box hands every key stroke to its internal editor and
consumes the original event. macOS offers a Command shortcut to the application
menu only if nothing inside the window handled it, so both shortcuts did nothing
as long as such a combo box had the focus - and the application starts with the
focus in the source path combo box, which made them look permanently dead even
after the application menu was added.

The two shortcuts are now routed around the three editable combo boxes (source
path, destination path and the DecentSampler template folder). All other key
strokes, among them the editing shortcuts like Command-C and Command-V, keep
their normal way into the editor.
…ample root (git-moss#288)

* Audition: play the preview at a sample root so phrase presets are heard as recorded

* Log a note when a preset plays its samples far off the keyboard middle
…images (git-moss#293)

The Contents dialog was limited to the formats where one file holds several
presets. It is just as useful for a folder of files which each hold one preset -
e.g. Elektron Tonverk or Synthstrom Deluge presets - since it lists the preset
names stored inside of the files, their zones, key ranges and categories, and
allows to listen to them before converting. The list of multi-preset formats is
therefore gone and the button is shown for every source format.

The tree grew a level for this: the folders below the source folder come first,
then the file - but only if it holds more than the one preset, otherwise a
folder of presets would be doubled by a folder per file - then the containers
inside of the file. The folders were part of the container path before and
therefore ended up *below* the file of a preset instead of above it. A preset
which is named differently than its file shows the file name as well, so that it
can still be told apart.

A format whose preset is not a file at all - the sample files of a folder become
one multi-sample - reports its folder as the source file. Selecting such a
preset read nothing, since the detection ran the folder through the file
detection which filters by file ending. Folders are now detected as folders.

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…it-moss#294)

A preset which comes from a bank carries the bank in front of its name. The
device has a field of its own for the bank, so the name is written without it -
except when an explicit Bank is configured, which replaces the bank of the
source: the name then keeps it, so that it is not lost entirely.

The name field of the device holds 32 characters and everything beyond that is
cut off, which is exactly the part that tells the presets of one bank apart. Two
presets of an E-mu library CD, 'Full Arco String - Arco Strings Lo' and
'... Hi', both ended up as 'Full Arco String - Arco Strings' and were
indistinguishable in the browser of the device.

The bank of the source is now dropped from the name as soon as the qualified
name does not fit, which is logged, and the file name carries it as before. A
name which fits is written unchanged.
…layed name (stacked on git-moss#294) (git-moss#295)

* Waldorf Quantum/Iridium: do not cut off the preset name at the bank

A preset which comes from a bank carries the bank in front of its name. The
device has a field of its own for the bank, so the name is written without it -
except when an explicit Bank is configured, which replaces the bank of the
source: the name then keeps it, so that it is not lost entirely.

The name field of the device holds 32 characters and everything beyond that is
cut off, which is exactly the part that tells the presets of one bank apart. Two
presets of an E-mu library CD, 'Full Arco String - Arco Strings Lo' and
'... Hi', both ended up as 'Full Arco String - Arco Strings' and were
indistinguishable in the browser of the device.

The bank of the source is now dropped from the name as soon as the qualified
name does not fit, which is logged, and the file name carries it as before. A
name which fits is written unchanged.

* Waldorf Quantum/Iridium: option to shorten the file names to the displayed name

The file name of a written preset keeps the full name of the source, which for
a preset of a bank is the bank plus the preset ('05000-Full Arco String - Arco
Strings Lo.qpat', 45 characters). That is what tells the files apart in a file
browser on the computer, but more than the import screen of the device shows:
measured on the display of an Iridium MK2, the file list of the import screen
displays about 43 characters and cuts off the rest - exactly the end that
tells the presets apart.

The new option names the file after the preset name which the device displays -
the same name which is written into the name field - and keeps the whole file
name, including the import number prefix and the '.qpat' ending, within a
budget of 40 characters. The example becomes '05000-Arco Strings Lo.qpat'. The
name field is not affected: 'Full Arco String - Swirly Arcos' stays complete in
the preset while its file becomes '05002-Full Arco String - Swirly Arc.qpat'.
The sample folder follows the same name, so file and folder keep matching.

Disabled by default. The name for the name field is now created once in
createPreset and passed to the writing method, since both uses need it.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…ested library or performance (git-moss#296)

* Write single presets when the destination cannot hold a library or a performance

Creating a library or a performance is an empty default operation for a
destination which does not support it, so everything that was detected got
collected and then silently dropped: the run reported "Finished" and left an
empty output folder. Converting 175 Elektron Tonverk presets to Waldorf QPAT
with '-l MyLib' wrote nothing, as did Kontakt multis to QPAT with
'-t performance'.

Both now fall back to what the destination can write - one preset per source,
and the instruments of a performance as presets - and log which of the two
happened. Destinations which do support libraries or performances are
untouched: the same Deluge folder to a DecentSampler library still writes one
dslibrary with identical contents, and to TX16Wx performances the same 2170
files.

The user interface keeps offering only the output types of the destination, so
this only becomes visible on the command line, which accepts '-l' and
'-t performance' for every destination.

* Fail with an error message instead of falling back to single presets

The destination capabilities are known before anything runs, so the command
line now rejects '-l' and '-t performance' for a destination which cannot
write them, before the detection starts. The single-preset fallback and its
log lines are gone; ConverterBackend is back to stock.
* NI Kontakt: apply the program-level group solo flag

The soloed state of the individual groups is only in effect when the
program has group solo enabled. Kontakt keeps the solo flags of the
groups when solo mode is switched off again, so a program with such
left-overs converted to the soloed groups only and silently dropped
every other group. The flag was already read and written but had no
getter and no consumer.

* Update CHANGELOG.md

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…ormat (git-moss#300)

Zones which occupy the same key and velocity range but disjoint
selector ranges are the classic Sampler round-robin representation
(the only one Live 10/11 has). Read them as round-robin groups instead
of stacked zones and write round-robin groups as selector slices when
the native Live 12 round-robin flag is not available or does not
apply.
Some hosts store a complete Ogg stream inside a WAV file and mark it
with one of the Ogg Vorbis WAVE format codes (0x674F-0x6751 and
0x676F-0x6771) - the sample files of the legacy DirectWave packs which
ship with FL Studio are such files. They were rejected as an
unsupported source format, because the data chunk holds no PCM.

The stream is now taken out of the data chunk and decoded with the Ogg
support which is already present, so no new dependency is needed. Only
a data chunk which really starts with an Ogg page is accepted; the
modes which store bare Vorbis packets are left rejected rather than
mis-read.
…ss#301)

* Add support for the FL Studio DirectWave format

Reads non-monolithic DWP programs from their binary block structure and
falls back to the sampled/Automap file name conventions for DWB banks.
Writes a program folder (DWP plus 16-bit WAV files) which FL Studio
Desktop loads directly and FL Studio Mobile imports. The DWP writer
reproduces a real FL Studio Desktop export byte-identically when fed
its own detected data. Implements the request of issue git-moss#70.

* Read the embedded samples of monolithic DWP files

The dwsanitizer project proves that monolithic files keep the same
preamble and block structure (its hardcoded offsets and its path scan
pattern are exactly the block envelope at 0x5A). The audio format block
fully describes the audio, therefore the embedded data is identified as
the unknown block whose size is exactly frameCount times bytesPerFrame.
16/24-bit integer data is taken as-is, 32-bit float is converted to
24-bit. Verified against synthesized monolithic structures; no real
monolithic specimen was available.

* Validate the DWP structure against the FL Studio Mobile factory programs

All six factory programs (format version 0x25) tokenize completely and
confirm the preamble size rule, the zone mapping layout and the zone
gain float. They also show that the bytes-per-frame field of the audio
format block is not reliable in that version, therefore the embedded
audio of monolithic files is now identified by frame count times
channels times 2, 3 or 4 bytes per sample instead.

* Add the FL Studio DirectWave rows to the feature support sheet

* Fix the crash on loading written DWP files and add loop support

The preamble field at 0x4A is the number of sample containers - proven
by all seven specimens - and DirectWave crashes with an access
violation when it does not match (observed with FL Studio 2026). The
creator wrote the template value into every file; it now patches the
real count. The writer is also remodeled on the FL Studio Mobile
factory programs: version 0x25, 100 zero-based parameter slots, fixed
shadow block sizes and all opaque container blocks copied verbatim
from a factory program instead of a user export with uninitialized
data.

The factory programs also revealed the loop fields: the audio format
block holds loop mode, start and end at offsets 20/24/28 (all
sustained factory programs are looped there; their WAV files carry no
smpl chunks). The detector now reads the program loop (WAV chunks
remain the fall-back) and the creator writes it.

* Read and write the DirectWave amplitude envelope

Differential analysis of the factory programs identified block 0x01FD
as the amplitude envelope (four floats: attack, decay, sustain and
release knob positions): the looped 'Electric' piano is the only
program with sustain 0 and a long decay - the classic way to make a
looped e-piano die away - and the release is nudged up on exactly the
two sustained instruments. The sustain level converts exactly; the
time knobs are mapped with a provisional cubic law (10 s * position^3)
until the DirectWave time hints can be measured. Envelopes which equal
the version defaults are skipped so the category default applies.
Also documents the localized but unverified filter blocks (0x01FB, the
type field is Off in every available specimen) and the 4x4 modulation
matrix (0x0204, enums unknown).

* Write the observed value population into the audio format stride field

The field at offset 12 of the audio format block held 2 (bytes per
frame of a mono sample) in written files - a value which no real
DirectWave file ever shows (the factory population is powers of two
from 4 to 128, loosely following the frame count). Write the same
power-of-two law instead so written files stay inside the observed
value population; the DirectWave editor is suspected to use this field
for its waveform display.

* Read and write the zone gain and panning

The gain float at offset 9 of the zone mapping is a linear amplitude
(1.0 = 0 dB, the 0.675 of a factory zone is ca. -3.4 dB) and the float
at offset 13 is the panning with 0.5 as the center. Not converting the
gain made converted E-mu programs unusable: EOS presets park spare
layers at -96 dB, which then played at full volume (a parked buzz-loop
layer turned a converted pad into a rattle). Gains above 0 dB are
clamped to 1.0 since higher values were never observed.

* Convert all five DirectWave loop modes

The DirectWave plug-in binary carries its enumerations as string tables
and its editor library carries the same lists as menu items with their
enum value in the VCL Tag property. The loop mode table explains the 0
and 2 of the specimens as 'Disabled' and 'Forward' and adds 'One-Shot',
'Sustained' (= loop until release) and 'Bounce' (= alternating), which
are now all converted in both directions. The filter type enum is
documented as well (0 = Off, 1 = Lowpass ... 7 = Vox), but the filter
stays unwritten until the position of the type field is verified.

* Read and write the zone filter

The filter block starts with the type as a 32-bit integer followed by
the cutoff and the resonance as knob positions. Since every specimen
has its filters off, the layout was settled by experiment: of three
probe programs which put a low-pass into the candidate fields, only the
integer-at-offset-0 variant silenced its zone (a low-pass at a
near-minimum cutoff mutes a saw wave) while its control zone stayed
audible. The cutoff is mapped exponentially from 20 Hz to 20 kHz, which
is consistent with the probe but not calibrated against the frequency
display of DirectWave.

Not converting the filter was the second half of the E-mu conversion
problem: the layers which the source filters down to a sub-bass thump
played back in full bandwidth.

* Document that the knob laws are not stored in the plug-in binary

Searched for the cutoff and envelope time laws: there are no lookup
tables and no minimum/maximum constant pairs; the parameter ranges are
built in code (the binary carries DirectWaveParameters.cpp and a
'RangeMin < RangeMax' assertion but no descriptor table which
references the parameter names). The search did yield the display
formats, which confirm the gain, panning and 'Ticks' decodings.

* Calibrate the filter cutoff knob law by measurement

The knob laws are not stored in the plug-in binary, so the cutoff range
was measured by listening: a program with ten zones which play the same
220 Hz saw wave through a low-pass filter, with the cutoff knob stepped
from 0.10 to 1.0, was played from the on-screen keyboard of the
plug-in. The fundamental starts to pass at knob 0.26 and the wave is
fully open at 0.66. An exponential range of 45 Hz to 20 kHz reproduces
the first at 219.7 Hz and puts the second at ca. 2.5 kHz, and it
explains the two quiet zones below it as well. The previously assumed
20 Hz bottom is ruled out - it would have kept the 220 Hz fundamental
muted at the zone where it audibly passes.

* Calibrate the envelope knob time law by measurement

Measured like the cutoff law: a program whose six zones differ only in
the decay knob (sustain 0, so the note dies while the key is held) next
to five reference zones whose fade-out is baked into their audio at
0.5, 1, 2, 4 and 8 seconds. The reference zones show the measurement to
be 3 % short, and after that correction the six decay times fit

  seconds = 9.07 * position ^ 3.96

within 3 %. The previously assumed cubic taper up to 10 seconds was
wrong by up to a factor of three at short times.

* Read and write monolithic DirectWave programs

A monolithic program keeps the same structure and adds one block per
sample container - tag 0x0206, the last block before the container
terminator - whose payload is the length of the audio, four unused
bytes and the audio as a FLAC stream. The tag was located in the block
writer of the plug-in binary (the payload length is a constant 8 bytes
before the tag for every fixed-size block, so the blocks without such a
constant are the variable-length ones) and then confirmed on 85
monolithic programs: in all 288 sample containers the length field
equals the payload size minus 8, the field at offset 4 is 0 and the
block sits between the last 0x0204 block and the terminator.

Reading decodes the FLAC stream, writing is a new 'Monolithic file'
option of the creator which embeds the samples instead of writing a
folder of WAV files - a single file is what FL Studio Mobile is easiest
to transfer to, which is what issue git-moss#70 asked for. A written program is
byte-structurally identical to a program saved by DirectWave itself.

Also adds the format version 0x24 of the legacy patch pack of FL
Studio, whose preamble is 4 bytes longer: the start of the block stream
is now located instead of assumed. Those files contain the only zones
with an engaged filter found anywhere - 111 low-pass zones - which
confirm the filter type encoding.

* Write monolithic DirectWave programs by default

Issue git-moss#70 asks for monolithic programs and its use case is FL Studio
Mobile, where a single file per instrument is the point. A monolithic
program is also smaller than the same instrument as a folder of WAV
files, since the embedded audio is FLAC compressed. The folder layout
stays available for the case that the samples should be editable.

* Always write monolithic DirectWave programs

Writing the samples as a folder of WAV files next to the DWP file had
one advantage - the samples stay editable - but that is what the WAV
and SFZ destinations are for. A monolithic program is a single
self-contained file, which is the point of git-moss#70 and of the FL Studio
Mobile use case, it is smaller because the embedded audio is FLAC
compressed, and it keeps up to 24 bit where the folder layout was
capped at 16. Writing only one layout removes the option and the second
code path with it.

Verified in DirectWave: a written monolithic program loads and all of
its zones play correctly.

* Name the DirectWave format after its host application

The tab was labeled only 'DirectWave', which does not say what the format
is; all other formats name their vendor or application. Use 'FL Studio
DirectWave' and sort the entry into the format lists accordingly (after
Fairlight, which is where the documentation and the feature sheet already
list it). The CLI format token stays 'DirectWave'.

* Build written DirectWave programs from a real monolithic program

The creator built its programs from the sample container of an FL Studio
Mobile factory program and zeroed the four program parameter blocks 0x0069
to 0x006C. Those blocks are not metadata: all 117 available programs carry
the same non-zero payloads, and the Mobile container brings the Mobile
defaults of the modulation matrix (Zone LFO 2 to Voice Pitch instead of
Pitch Bend to Voice Pitch), of the two LFOs and of the disabled filter.
Written programs loaded and played, but lost the attack of the source
instrument.

Take the preamble and the container template from a real monolithic desktop
program instead, so a written file equals a real program byte for byte
except for the six blocks which carry the converted content, and write the
observed payloads of the program parameter blocks. The modulation matrix
indices are decoded in the design document; the path of the program itself
uses a single backslash like the real files.

Also pad the embedded audio to a full block: the frame count of every sample
of every monolithic program is a multiple of 512 and its FLAC stream is one
block longer. A sample which does not fill its last block crashes the plug-in
while loading - converting an instrument with resampling produces such
lengths, which is how it was found.
* Add support for the FL Studio DirectWave format

Reads non-monolithic DWP programs from their binary block structure and
falls back to the sampled/Automap file name conventions for DWB banks.
Writes a program folder (DWP plus 16-bit WAV files) which FL Studio
Desktop loads directly and FL Studio Mobile imports. The DWP writer
reproduces a real FL Studio Desktop export byte-identically when fed
its own detected data. Implements the request of issue git-moss#70.

* Read the embedded samples of monolithic DWP files

The dwsanitizer project proves that monolithic files keep the same
preamble and block structure (its hardcoded offsets and its path scan
pattern are exactly the block envelope at 0x5A). The audio format block
fully describes the audio, therefore the embedded data is identified as
the unknown block whose size is exactly frameCount times bytesPerFrame.
16/24-bit integer data is taken as-is, 32-bit float is converted to
24-bit. Verified against synthesized monolithic structures; no real
monolithic specimen was available.

* Validate the DWP structure against the FL Studio Mobile factory programs

All six factory programs (format version 0x25) tokenize completely and
confirm the preamble size rule, the zone mapping layout and the zone
gain float. They also show that the bytes-per-frame field of the audio
format block is not reliable in that version, therefore the embedded
audio of monolithic files is now identified by frame count times
channels times 2, 3 or 4 bytes per sample instead.

* Add the FL Studio DirectWave rows to the feature support sheet

* Fix the crash on loading written DWP files and add loop support

The preamble field at 0x4A is the number of sample containers - proven
by all seven specimens - and DirectWave crashes with an access
violation when it does not match (observed with FL Studio 2026). The
creator wrote the template value into every file; it now patches the
real count. The writer is also remodeled on the FL Studio Mobile
factory programs: version 0x25, 100 zero-based parameter slots, fixed
shadow block sizes and all opaque container blocks copied verbatim
from a factory program instead of a user export with uninitialized
data.

The factory programs also revealed the loop fields: the audio format
block holds loop mode, start and end at offsets 20/24/28 (all
sustained factory programs are looped there; their WAV files carry no
smpl chunks). The detector now reads the program loop (WAV chunks
remain the fall-back) and the creator writes it.

* Read and write the DirectWave amplitude envelope

Differential analysis of the factory programs identified block 0x01FD
as the amplitude envelope (four floats: attack, decay, sustain and
release knob positions): the looped 'Electric' piano is the only
program with sustain 0 and a long decay - the classic way to make a
looped e-piano die away - and the release is nudged up on exactly the
two sustained instruments. The sustain level converts exactly; the
time knobs are mapped with a provisional cubic law (10 s * position^3)
until the DirectWave time hints can be measured. Envelopes which equal
the version defaults are skipped so the category default applies.
Also documents the localized but unverified filter blocks (0x01FB, the
type field is Off in every available specimen) and the 4x4 modulation
matrix (0x0204, enums unknown).

* Write the observed value population into the audio format stride field

The field at offset 12 of the audio format block held 2 (bytes per
frame of a mono sample) in written files - a value which no real
DirectWave file ever shows (the factory population is powers of two
from 4 to 128, loosely following the frame count). Write the same
power-of-two law instead so written files stay inside the observed
value population; the DirectWave editor is suspected to use this field
for its waveform display.

* Read and write the zone gain and panning

The gain float at offset 9 of the zone mapping is a linear amplitude
(1.0 = 0 dB, the 0.675 of a factory zone is ca. -3.4 dB) and the float
at offset 13 is the panning with 0.5 as the center. Not converting the
gain made converted E-mu programs unusable: EOS presets park spare
layers at -96 dB, which then played at full volume (a parked buzz-loop
layer turned a converted pad into a rattle). Gains above 0 dB are
clamped to 1.0 since higher values were never observed.

* Convert all five DirectWave loop modes

The DirectWave plug-in binary carries its enumerations as string tables
and its editor library carries the same lists as menu items with their
enum value in the VCL Tag property. The loop mode table explains the 0
and 2 of the specimens as 'Disabled' and 'Forward' and adds 'One-Shot',
'Sustained' (= loop until release) and 'Bounce' (= alternating), which
are now all converted in both directions. The filter type enum is
documented as well (0 = Off, 1 = Lowpass ... 7 = Vox), but the filter
stays unwritten until the position of the type field is verified.

* Read and write the zone filter

The filter block starts with the type as a 32-bit integer followed by
the cutoff and the resonance as knob positions. Since every specimen
has its filters off, the layout was settled by experiment: of three
probe programs which put a low-pass into the candidate fields, only the
integer-at-offset-0 variant silenced its zone (a low-pass at a
near-minimum cutoff mutes a saw wave) while its control zone stayed
audible. The cutoff is mapped exponentially from 20 Hz to 20 kHz, which
is consistent with the probe but not calibrated against the frequency
display of DirectWave.

Not converting the filter was the second half of the E-mu conversion
problem: the layers which the source filters down to a sub-bass thump
played back in full bandwidth.

* Document that the knob laws are not stored in the plug-in binary

Searched for the cutoff and envelope time laws: there are no lookup
tables and no minimum/maximum constant pairs; the parameter ranges are
built in code (the binary carries DirectWaveParameters.cpp and a
'RangeMin < RangeMax' assertion but no descriptor table which
references the parameter names). The search did yield the display
formats, which confirm the gain, panning and 'Ticks' decodings.

* Calibrate the filter cutoff knob law by measurement

The knob laws are not stored in the plug-in binary, so the cutoff range
was measured by listening: a program with ten zones which play the same
220 Hz saw wave through a low-pass filter, with the cutoff knob stepped
from 0.10 to 1.0, was played from the on-screen keyboard of the
plug-in. The fundamental starts to pass at knob 0.26 and the wave is
fully open at 0.66. An exponential range of 45 Hz to 20 kHz reproduces
the first at 219.7 Hz and puts the second at ca. 2.5 kHz, and it
explains the two quiet zones below it as well. The previously assumed
20 Hz bottom is ruled out - it would have kept the 220 Hz fundamental
muted at the zone where it audibly passes.

* Calibrate the envelope knob time law by measurement

Measured like the cutoff law: a program whose six zones differ only in
the decay knob (sustain 0, so the note dies while the key is held) next
to five reference zones whose fade-out is baked into their audio at
0.5, 1, 2, 4 and 8 seconds. The reference zones show the measurement to
be 3 % short, and after that correction the six decay times fit

  seconds = 9.07 * position ^ 3.96

within 3 %. The previously assumed cubic taper up to 10 seconds was
wrong by up to a factor of three at short times.

* Read and write monolithic DirectWave programs

A monolithic program keeps the same structure and adds one block per
sample container - tag 0x0206, the last block before the container
terminator - whose payload is the length of the audio, four unused
bytes and the audio as a FLAC stream. The tag was located in the block
writer of the plug-in binary (the payload length is a constant 8 bytes
before the tag for every fixed-size block, so the blocks without such a
constant are the variable-length ones) and then confirmed on 85
monolithic programs: in all 288 sample containers the length field
equals the payload size minus 8, the field at offset 4 is 0 and the
block sits between the last 0x0204 block and the terminator.

Reading decodes the FLAC stream, writing is a new 'Monolithic file'
option of the creator which embeds the samples instead of writing a
folder of WAV files - a single file is what FL Studio Mobile is easiest
to transfer to, which is what issue git-moss#70 asked for. A written program is
byte-structurally identical to a program saved by DirectWave itself.

Also adds the format version 0x24 of the legacy patch pack of FL
Studio, whose preamble is 4 bytes longer: the start of the block stream
is now located instead of assumed. Those files contain the only zones
with an engaged filter found anywhere - 111 low-pass zones - which
confirm the filter type encoding.

* Write monolithic DirectWave programs by default

Issue git-moss#70 asks for monolithic programs and its use case is FL Studio
Mobile, where a single file per instrument is the point. A monolithic
program is also smaller than the same instrument as a folder of WAV
files, since the embedded audio is FLAC compressed. The folder layout
stays available for the case that the samples should be editable.

* Always write monolithic DirectWave programs

Writing the samples as a folder of WAV files next to the DWP file had
one advantage - the samples stay editable - but that is what the WAV
and SFZ destinations are for. A monolithic program is a single
self-contained file, which is the point of git-moss#70 and of the FL Studio
Mobile use case, it is smaller because the embedded audio is FLAC
compressed, and it keeps up to 24 bit where the folder layout was
capped at 16. Writing only one layout removes the option and the second
code path with it.

Verified in DirectWave: a written monolithic program loads and all of
its zones play correctly.

* Name the DirectWave format after its host application

The tab was labeled only 'DirectWave', which does not say what the format
is; all other formats name their vendor or application. Use 'FL Studio
DirectWave' and sort the entry into the format lists accordingly (after
Fairlight, which is where the documentation and the feature sheet already
list it). The CLI format token stays 'DirectWave'.

* Build written DirectWave programs from a real monolithic program

The creator built its programs from the sample container of an FL Studio
Mobile factory program and zeroed the four program parameter blocks 0x0069
to 0x006C. Those blocks are not metadata: all 117 available programs carry
the same non-zero payloads, and the Mobile container brings the Mobile
defaults of the modulation matrix (Zone LFO 2 to Voice Pitch instead of
Pitch Bend to Voice Pitch), of the two LFOs and of the disabled filter.
Written programs loaded and played, but lost the attack of the source
instrument.

Take the preamble and the container template from a real monolithic desktop
program instead, so a written file equals a real program byte for byte
except for the six blocks which carry the converted content, and write the
observed payloads of the program parameter blocks. The modulation matrix
indices are decoded in the design document; the path of the program itself
uses a single backslash like the real files.

Also pad the embedded audio to a full block: the frame count of every sample
of every monolithic program is a multiple of 512 and its FLAC stream is one
block longer. A sample which does not fill its last block crashes the plug-in
while loading - converting an instrument with resampling produces such
lengths, which is how it was found.

* Convert the pitch and the volume LFO of DirectWave zones

A DirectWave zone has two LFOs which are routed to their target in the
modulation matrix of the zone. Convert both directions for the two targets
the model knows: LFO 1 modulates the pitch (vibrato) and LFO 2 the volume
(tremolo). The blocks were identified from the corpus - 'Electric' is the
only program which routes a zone LFO to something audible and also the only
one whose LFO rate differs from the default, and the four Acoustic Guitar
programs with a filter envelope show that the neighboring block is the pair
of zone envelopes, not the LFOs.

The three laws are measured, since they are not in the plug-in binary: the
rate knob is 20 * position^2 Hertz, the modulation amount is bipolar around
0.5 with the strength being the cube of the distance from it, and a pitch
modulation reaches 2400 cent at the full strength while a gain modulation is
the modulation index of the linear gain. Each was measured with a probe
program of eight zones which differ in exactly one value; the measurements
and the fits are in the design document.

* Confirm the LFO laws against the plug-in and a second volume probe

The volume law rested on a single usable measurement; a probe with eight
strengths spread over the measurable range confirms it across the whole
range. A converted program played back by DirectWave confirms the rate and
the pitch law end to end and reproduces the written modulation index.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…it-moss#302)

* Add support for the Teenage Engineering OP-XY format

The multi-sample presets of the OP-XY are open: a preset is a folder
with the ending '.preset' which holds all samples as WAV files and a
'patch.json' with the mapping and the settings of the engine. See
issue git-moss#72, which contains an example of the description file.

Reading gives the key range, root key, tuning, gain, play range and
loop of every region plus the amplitude envelope and the pitch bend
range, which the device stores once for the whole preset. Writing
creates such a folder, which only needs to be copied to the device.

Two limits of the format are handled explicitly: it maps only across
the keyboard, so of several zones which cover the same keys the one
which plays at the highest velocity is kept, and the device plays at
most 24 regions. The regions are written the way the device expects
them, covering the keyboard without gaps - a region ends at its own
upper key and starts one key above the previous one.

Verified by converting an E-mu Emulator X bank of 509 presets: 5152
regions, no structural problems, no gaps and no duplicate key ranges,
and all 509 written presets read back with their key ranges and root
keys unchanged.

* OP-XY: report samples which exceed the length the device plays

The device manual states that a sample may be at most 20 seconds long
(and that a multi-sample preset holds at most 24 zones, which was
already handled). Samples are not truncated silently - the number of
too long samples is reported so that the trim processing option can be
used.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
douglas-carmichael and others added 24 commits September 19, 2026 16:11
…h click' (git-moss#459)

The snapper only moves loops which click since it was reworked in 20.3.0,
but the label read as if every loop were moved to a zero-crossing. The
label, the log line, the note about clicking loops, the manual and the
20.3.0 changelog use the new name; -Zs is unchanged.
…me is read (git-moss#458)

TAL-Sampler plays the volume of a sample as a linear amplitude factor
relative to 0.7843 (1.0 is +2.1 dB), measured by rendering the plug-in
offline at eight volumes; it was read and written as if 0.353 were -12 dB
and 1.0 were +6 dB. The main volume of a program (40 log(2x) dB, like the
volume of a layer) was not read at all. Of the 598 presets of Espen Kraft's
eight TAL volumes the typical one was converted 3.9 dB too loud, 'Kawai PHM
Digi StringChoir' 5.0 dB, and 43 up to 4.8 dB too quiet.

When writing, the main volume and then the volume of the layers carry the
gain above the +2.1 dB of a sample, up to +26 dB.
* TAL Sampler: encrypted samples (*.talwav) are decrypted

The factory library of TAL-Sampler and many sample packs deliver their
samples as .talwav files, which the plug-in creates with 'Convert wav
Samples to talwav'. Such presets were rejected with 'Encrypted samples are
not supported'.

A .talwav file is the WAV file encrypted with Blowfish in ECB mode with a
fixed 72-byte key; the halves of a block are little-endian words and the
bytes behind the last whole block are not encrypted. The plug-in appends
to an existing file, so a file converted more than once holds several
copies; the first one is read, as the plug-in plays it.

Blowfish is implemented in file/Blowfish, since the cipher of the Java
Cryptography Architecture accepts keys of up to 56 bytes only. The format
is documented in documentation/design/TAL_SAMPLER_TALWAV_FORMAT.md.

* Clarify TAL-Sampler .talwav format documentation

Focus the design notes on decoder requirements, format behavior and interoperability verification.
…ade alone, like the snapping (git-moss#461)

The note about loops which click only skipped loops with a cross-fade of
at least 1% of the loop, while 'Snap loops which click' leaves every loop
with a cross-fade alone. A loop with a shorter cross-fade was therefore
skipped by the snapping ('0 adjusted') and reported by the note, which
recommended the snapping; 'Only loops which click' replaced its
cross-fade. Both now use the same check, so they always agree about a
loop, as the note was documented to do.

When the loops were snapped, the note now says that the loops it names
still click after the snapping instead of recommending it.

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…nfiltered (git-moss#462)

TAL-Sampler has one filter and switches each layer through it or not. The
reader applied the filter to all layers if the first one is switched
through it and to none otherwise; 83 of the 1,675 presets on the test
machine filter only some of their layers. Only the zones of the layers
which are switched through the filter get it now, and the writer switches
only the layers through it whose zones have a filter.

Waldorf Quantum/Iridium: an oscillator whose zones have no filter while
other zones of the layer have one is written with the destination 'VCA',
which sends it directly to the amplifier, around the filter, and such an
oscillator is read without the filter. The filter, its wheel modulation
and its LFO are taken from the first zone which has a filter, and a
filtered and an unfiltered group are not merged into one oscillator.

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…into the layers, 2 layers by default (git-moss#463)

* Waldorf Quantum/Iridium: warn when the groups of a preset need more layers than the option allows

The writer puts every group which needs an oscillator of its own on one,
3 per layer, and adds the groups beyond the maximum number of layers of
the option to the last sample map. Entries which overlap in one sample
map alternate on successive notes instead of sounding together, so such a
preset silently played two of its layers alternately.

A warning now names the preset, the samples which are added and the
number of layers which keeps them apart - or that even the 4 layers of
the device are not enough. The written files do not change.

* Waldorf Quantum/Iridium: the maximum number of layers is 2 by default

With the default of 1 layer, a preset which needs more than the 3
oscillators of one layer played two of its groups alternately, e.g. 7 of
the 32 presets of Michael Oakley's TAL-Sampler pack. Every instrument of
the family plays 2 layers, and a preset which fits into one layer is
still written as one. An explicit setting of the replaced option which
leaves the second layer off still selects 1 layer.

The choices of the option say what they are for: '1 layer
(stackable)', '2 layers (all models)'. The tooltip of the author and the
bank is shorter, so that it fits on small screens.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…git-moss#464)

The decay of the envelopes was converted with the time law of the attack
and the release, 7.2 x^4 seconds. Rendered offline (4.7.2), the decay of
TAL-Sampler approaches the sustain level exponentially and much slower:
with a sustain of 0, the values 0.3 to 0.8 reach -6 dB after 0.11, 0.35,
0.70, 1.35, 2.70 and 4.56 seconds and every further 6 dB after the same
time again, and towards a sustain of 0.5 the time constant is the same.

A decay is now converted to the linear decay which reaches -20 dB at the
same moment as the one of the plug-in, 37.1 x^3.75 seconds (within 9 % of
the measurements), and written back with the inverse law. Sounds which
decay to silence were far too short, e.g. 'Casiowave' of Michael Oakley's
pack decayed in 0.12 instead of 0.8 seconds.

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…it-moss#471)

* DecentSampler: a preset with a repeated attribute could not be read

A preset which sets the same attribute twice on one element was rejected
by the XML parser and skipped as a whole, e.g. 'Could not parse XML
(line: 36, column: 54): Attribute "textColor" was already specified for
element "labeled-knob"'. DecentSampler loads such a preset: it is built
on JUCE, whose XML parser appends every attribute it reads and returns
the first one with the requested name. Presets are often written by
hand, where such a repetition is easy to produce - all seven presets of
Harry Zimm's 'Rare Akai AX73' carry a second 'textColor' on the reverb
knob of their user interface, and none of them could be converted.

A repeated attribute is now ignored and its first value used, for
preset files as well as for the presets inside a library. The line
breaks of the file are kept, so a further error is still reported at
its line, and the log names the attribute and the element. Presets
without a repetition are unchanged: the seven presets written by the
DecentSampler destination read back without a message.

* DecentSampler: a repeated attribute is only logged if it matters

The message about a repeated attribute was logged for every repetition,
also for the second 'textColor' on the reverb knob of the 'Rare Akai
AX73' presets, which only colours the user interface and makes no
difference to the conversion. It is now logged only if the two values
differ and the attribute is one which the conversion reads. With 'Log
unused XML elements and attributes' every repetition with a differing
value is logged. A repetition of the same value is never logged, since
either value gives the same result.

The table of the attributes which the conversion reads now also lists
the elements tag, effect, envelope, lfo and binding. Their attributes
were read with literal names or a private constant; they use the
constants of the table now.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…older (git-moss#472)

With 'Create folder structure', the presets of a dsbundle were written
into an output folder named like the bundle, e.g. 'Harry Zimms Rare
Akai AX73 Synthesizer.dsbundle/asgard.sfz', which the Finder shows as a
bundle file again; the DecentSampler destination with 'Create Bundle'
even nested its bundles inside it. A dsbundle is a folder which
DecentSampler treats as one file, like a dslibrary, whose presets
already went into the folder which contains the library file.

A detector can now declare a folder as a package which the format
treats as one file; such a folder is left out of the sub-path of the
output, its name is still used for the detection of the metadata like
any folder name. The DecentSampler detector declares the dsbundle. The
presets of a bundle go into the folder which contains the bundle, also
when the bundle is the selected source folder or has sub-folders. The
Contents dialog matches the folders of a file against the sub-path in
their order, since both do not match one to one any more, and shows
the bundle as the folder it is.

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
)

The exclusive groups of the model were neither written nor read, so
e.g. the open and closed hi-hats of a drum kit which was converted to
SFZ played on top of each other.

A region of an exclusive group now carries its number in the 'group'
and the 'off_by' opcode, so that the regions of the group switch each
other off. The opcodes are promoted to the group and global level like
the other opcodes. When reading, a region whose 'group' and 'off_by'
are equal gets that exclusive group. A region which is switched off by
another group than its own, e.g. an open hi-hat which the closed one
stops but not vice versa, cannot be expressed as an exclusive group and
is read without one.

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…ency is lowered (git-moss#476)

The band-limited conversion of the sample frequency overshoots at steep
edges, e.g. of square waves, therefore the audio of a sample which peaks
close to full scale exceeded the range of its bit depth and was clipped.
E.g. 150 of the 160 tone samples of the Speak & Music library, which
peak at about -1 dBFS, overshot by up to 1.5 dB when converted from
48 to 44.1 kHz.

The processing options now lower such a sample until it fits and add the
attenuation to the gain of its zone, so that its loudness stays the same.
The conversion which a destination format applies when writing its own
sample frequency cannot adjust the zone and still clips.

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
git-moss#477)

* Processing: the samples can be transposed onto the pitch they sound at

A sample library is often built from an instrument which was itself
transposed - another footage, a frequency ratio of one half or two, a
transposed patch - so that the recording which is mapped to C3 sounds one
or two octaves away from it. The mapping is faithful and the source plays
exactly the same way, but on the destination device the preset is then
played in another part of the keyboard than the player expects, and a
fixed transposition cannot fix a library in which only some presets are
affected.

The pitch of a sample is now measured and a note reports how far the
samples of a multi-sample sound from their root keys. The Transpose
processing option set to 'Auto' (-Zpa) moves the root keys of each
multi-sample onto its measured pitch; only the root keys are moved, the
audio, the key ranges and the tuning of the zones stay as they are.

The pitch is the highest fundamental whose harmonics hold at least 85% of
the power of the spectrum: a candidate an octave too low explains the same
partials but predicts harmonics between them which are not there, and one
an octave too high is only taken if the odd harmonics are practically
absent, which is then the pitch which is heard. It is measured in up to
twelve zones spread over the multi-sample, in the loop or in the loudest
part after the attack, and a result needs at least three measurable zones
of which at least 80% agree - material which is not clearly pitched and
kits whose zones are each rooted differently get no result.

* Manual: when to use the transposition to the measured pitch

Whether a transposition is wanted is a decision and not a defect - an
instrument is often played one or two octaves away from the key - so the
option is meant for the presets which need it and not for a whole library,
where it would move the bass patches as well. The note is logged without
the option, so a plain conversion already reports which presets sound away
from their keys, and every transposed multi-sample is logged with its
value.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…sample slots (git-moss#478)

* Groove Synthesis 3rd Wave: read and write the sample slots

The oscillators of the 3rd Wave play sample slots (S00, S01, ...), which
hold up to 8 mono samples, each with its key range around an assigned
note, fine tuning, volume, play range and loop. The device stores a slot
as a multi-sample file (*.bin) and a program together with the slots and
wavetables it uses as a unified program file (*.pgdata).

Reading takes the multi-sample files (versions 1, 4 and 5) and the sample
slots of unified program files. Each slot becomes a multi-sample; key
range, assigned note, fine tuning, volume, play range and the loop with
its cross-fade are read, 'looping on when note on' as a loop which ends
at the release of the note. The settings of the program are not
converted.

Writing creates multi-sample files for 'Import multisample' and 'Bulk
multisample import'. Samples which do not fit into one slot - more than
8, layered samples like velocity layers or round robins, or more than the
sample memory - are written as further, numbered slots. The samples
become 16 bit mono with 10 to 48 kHz; a looped sample outside of that
range is converted together with its loop, for which
AbstractCreator#recalculateSamplePositions got a variant which takes a
range of sample rates. Coarse tuning moves the assigned note, the key
range is limited to two octaves around it and the gain is applied to the
audio, up to its peak level, since the format has no volume.

Tested with the files of the manufacturer: the 45 programs of the sample
library and the 19 unified programs of the factory banks are read to
their exact end (88 slots, 378 samples), as are the six multi-sample
files of the OS 1.8 sample presets; the version 4 file among them is
written back byte-identical. Written as multi-sample files, all 270
samples of the sample library keep audio, play range, loop, key range
and tuning, except for 31 equal-power cross-fades (written as equal
volume), five 12 bit settings and one key range beyond two octaves. The
written files are not yet verified on hardware.

* Groove Synthesis 3rd Wave: write unified program files

A multi-sample file needs to be imported into a sample slot and a
program has to be set up to play it. A unified program file carries a
program together with the samples it plays and loads with a single
import, the way the sample library of the manufacturer is distributed.
It is now the default output; the multi-sample files remain available
with the new 'Output' option (-p ThirdWaveOutputFormat=bin).

The program is the init program of the instrument, stored as a resource
in the program file format of the instrument. Part N plays sample slot
N with its first oscillator at full level, its other oscillators are
silent. A multi-sample with more than 8 samples is split into up to 4
slots and the parts split the keyboard between them. Velocity layers and
round robins cannot be played by a program, only the first layer is
written. The samples store their gain as their volume (up to +6 dB)
instead of applying it to the audio. A program whose samples do not fit
into the sample memory of the device is not written.

Written as programs, the 65 slots of the sample library of the
manufacturer keep the audio, volume, play range, loop, key range and
tuning of all 270 samples, except for one key range beyond two octaves;
read back, the written programs differ from the init program only in
their oscillators and the split of the keyboard. The written files are
not yet verified on hardware.

* Groove Synthesis 3rd Wave: read programs as instruments, write layers to parts

A unified program file is now read as one multi-sample which plays like
the program: each oscillator of a part which plays a sample slot becomes
a group with the level (oscillator level x part volume), panning and
tuning (coarse and fine tuning of the oscillator plus the fine tuning of
the part) of its part, on the keys of the keyboard sections which the
part plays, shifted by the transposition of its keyboard. Stereo
programs (two parts panned to both sides), split programs and layered
programs therefore arrive as one instrument instead of one multi-sample
per slot. An oscillator which does not follow the keyboard plays its
samples at a fixed pitch. A slot of which the program does not play all
samples is read on its own as well; a program whose parameters have an
unknown layout is read as its slots.

When a program is written, samples which play at the same time get
parts of their own: the two channels of a stereo sample, panned to both
sides, or a second sound on the same keys. A part gets the tuning which
most of its samples share on its oscillator and the level of its
loudest sample on the oscillator level and the part volume, so gains up
to +10.6 dB leave the audio unchanged. Samples are grouped by their
tuning as well if their assigned notes cannot take the difference. Parts
whose slots hold the same samples share one slot, and a layer whose
slots split the keyboard at other notes than the main layer plays all
sections which its samples reach. Velocity layers and round robins are
still reduced to the loudest layer and the first round.

All 64 stock programs, read as instruments, written as programs and read
back, play the same on every key except for the two-octave key range of
one stock sample and one oscillator with a fixed pitch; the 18 layered
programs of the sample library also keep what every key plays through
SFZ and DecentSampler.

* Groove Synthesis 3rd Wave: design document, the SFZ round trip of one program

One part of 'Hanger Drums and FM' is tuned by +36.93 semi-tones, which the SFZ reader limits to 36 when the written preset is read back.

* Groove Synthesis 3rd Wave: combine doubled samples, keep both sides when parts run out

Samples which play the same audio in the same way on the same keys,
e.g. a layer which a preset doubles, are written as one sample with the
sum of their volumes: they sound the same and need one part instead of
two.

If the samples of a program still need more than its 4 parts, the parts
are given layer by layer - the first voice of each panning, then the
second one of each. In a layer, voices which are panned to both sides by
the same amount, e.g. the two channels of a stereo sample, come first
and get their slots together from the lowest one on, so both sides keep
the same keys. Before, the voices got parts in the order of their
panning, so the right channel of a stereo layer was the first to be left
out. A TAL-Sampler preset with a doubled layer on each side and two
centred sounds needed 7 parts and kept only the left side; it now needs
5 and keeps the left and the right layer and both centred sounds.

* Groove Synthesis 3rd Wave: the format is selected by the unified program files

The multi-sample files end with .bin, which many devices use, e.g. the
pad configuration of the Roland SP-404MK2 (PADCONF.BIN): picking such a
file selected the 3rd Wave as the source format. The format list and the
selection of the format by the ending of a picked file now use .pgdata,
the unified program files in which Groove Synthesis distributes its
sample library. The .bin files are read as before, from a folder and as
a picked file while the 3rd Wave is selected.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
The gain of a group is written as the volume of the oscillator which
plays it, and the same amount is taken off the entries of its sample
map so that it is not applied twice. The volume of an oscillator ends
at 0 dB, though: the part of a higher gain was clamped away there, but
still taken off the sample map, so it was lost. A DecentSampler preset
whose two layers are raised by 1.7 and 2.3 dB arrived that much quieter
and with the balance between its layers changed.

Only the part of a group gain up to 0 dB goes to the oscillator now; the
rest stays in the entries of the sample map, which carry a gain above
0 dB anyway. Read back, the presets keep the gain of every layer.

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
  * New: Added support for reading/writing version 3.8 - 3.20 features (round-robin, one-shot). Writing now always uses version 3.20.
  * New: Add support for LFOs (unfinished)
  * Fixed: Read version is now output in decimal not hexadecimal.
* Consolidation.
A dsproduct file contains the XML document of a dspreset in a binary
container: the header DECENTSAMPLER001 followed by the document, which
is encrypted with Blowfish in ECB mode (little-endian block halves) and
padded to whole blocks. It is decoded in memory and read like a
dspreset, also inside of dslibrary files and dsbundle folders. Unknown
versions and truncated or damaged files are reported, the other presets
of a library are still read.

DecentSampler sends the stored values of the controls of the user
interface to the parameters which they are bound to when it loads a
preset, therefore these values are applied before the conversion, with
linear, table and fixed-value translations: amplitude envelope, volume,
pan, tuning, key and velocity tracking, enabling groups and tags, tag
volume and polyphony, filter frequency and resonance, the gain effect
and the amount and rate of modulators.

Further:
- The settings of the groups element and of a group are the defaults of
  their samples, e.g. velocity ranges which are set on the groups were
  ignored before.
- The pan of a sample replaces the one of its group, the pan offsets of
  the instrument and of a group (globalPan, groupPan) are added. The
  volume of the groups element, the tag volumes, the gain effects and
  the volume of the main output are applied.
- Groups and samples with a disabled tag are skipped like disabled
  groups.
- A sample without the amplitude envelope (ampEnvEnabled) becomes a
  one-shot, samples which silence each other with a tag (silencedByTags)
  an exclusive group.
- If a preset sets its amplitude envelope, the curves which are not set
  get the defaults of DecentSampler (logarithmic attack, exponential
  decay and release). Without any envelope setting the default envelope
  of the category is applied as before.
- The features which change the sound but cannot be converted, e.g. a
  convolution, are listed in one message per preset.
…ten (git-moss#474)

* DecentSampler: product presets (dsproduct) are read

A dsproduct file contains the XML document of a dspreset in a binary
container: the header DECENTSAMPLER001 followed by the document, which
is encrypted with Blowfish in ECB mode (little-endian block halves) and
padded to whole blocks. It is decoded in memory and read like a
dspreset, also inside of dslibrary files and dsbundle folders. Unknown
versions and truncated or damaged files are reported, the other presets
of a library are still read.

DecentSampler sends the stored values of the controls of the user
interface to the parameters which they are bound to when it loads a
preset, therefore these values are applied before the conversion, with
linear, table and fixed-value translations: amplitude envelope, volume,
pan, tuning, key and velocity tracking, enabling groups and tags, tag
volume and polyphony, filter frequency and resonance, the gain effect
and the amount and rate of modulators.

Further:
- The settings of the groups element and of a group are the defaults of
  their samples, e.g. velocity ranges which are set on the groups were
  ignored before.
- The pan of a sample replaces the one of its group, the pan offsets of
  the instrument and of a group (globalPan, groupPan) are added. The
  volume of the groups element, the tag volumes, the gain effects and
  the volume of the main output are applied.
- Groups and samples with a disabled tag are skipped like disabled
  groups.
- A sample without the amplitude envelope (ampEnvEnabled) becomes a
  one-shot, samples which silence each other with a tag (silencedByTags)
  an exclusive group.
- If a preset sets its amplitude envelope, the curves which are not set
  get the defaults of DecentSampler (logarithmic attack, exponential
  decay and release). Without any envelope setting the default envelope
  of the category is applied as before.
- The features which change the sound but cannot be converted, e.g. a
  convolution, are listed in one message per preset.

* DecentSampler: one-shots, exclusive groups and linear curves are written

- A one-shot sample switches off its amplitude envelope
  (ampEnvEnabled="false"), with which DecentSampler plays it to its end.
  A looped sample is not written as a one-shot, since it would never
  stop then.
- The samples of an exclusive group carry a tag by which they silence
  each other (tags and silencedByTags). A sample keeps the tag which
  limits the number of voices.
- A curve of the amplitude envelope which is not set is logarithmic for
  the attack and exponential for the decay and the release in
  DecentSampler. The curves of an envelope are therefore always written,
  also a linear curve (0), which was left out and played curved.
- If all groups had the same amplitude envelope, it was written to every
  sample instead of once for the instrument (AbstractCreator
  .getAmpEnvelopeParamLevel returned the zone level). The envelope knobs
  of the user interface, which are bound to the instrument, then started
  at default values and did not change the sound.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
…it-moss#480)

'Snap loops which click' now comes before the loop cross-fade, and the
check box next to the cross-fade reads 'Only loops which still click':
together they snap the loops which click and cross-fade only the loops
which snapping could not fix. The drop-down is labelled 'Cross-fade loops'
instead of 'Set fixed loop-crossfade', and the check box is disabled while
no cross-fade above 0% is selected, since it does nothing without one.

The log counts the loops which snapping moved instead of 'adjusted' ones,
the notes about clicking loops name 'Only loops which still click', and
loops which still click because they start at the beginning of their
sample get a note which says so.
…2 bit audio (git-moss#481)

The median frame-to-frame movement around a loop boundary was multiplied by the sample rate
with integers, which overflows for loud 24 and 32 bit audio, e.g. a median step of 74,000 at
48 kHz. The movement turned negative, so every jump above 2% of the level counted as a click:
the note reported such loops and the loop cross-fade for the loops which click was set on them.
After snapping, the 834 TAL-Sampler presets on the test machine report 124 instead of 456 loops,
nearly the 123 which they report when their samples are reduced to 16 bit.
…clicking or snapped into a click (stacked on git-moss#480) (git-moss#482)

* Process dialog: list the loop options in the order they are applied

'Snap loops which click' now comes before the loop cross-fade, and the
check box next to the cross-fade reads 'Only loops which still click':
together they snap the loops which click and cross-fade only the loops
which snapping could not fix. The drop-down is labelled 'Cross-fade loops'
instead of 'Set fixed loop-crossfade', and the check box is disabled while
no cross-fade above 0% is selected, since it does nothing without one.

The log counts the loops which snapping moved instead of 'adjusted' ones,
the notes about clicking loops name 'Only loops which still click', and
loops which still click because they start at the beginning of their
sample get a note which says so.

* Loop click note: a loop only clicks if its wrap also breaks the curve of the waveform

The jump from the last frame of a loop to its first frame is large in a steep part of the
waveform even where the loop continues seamlessly, e.g. at the edge of a square or pulse wave.
Such loops were reported as clicking and got the loop cross-fade of 'Only loops which still
click', although 'Snap loops which click' leaves them alone as clean. A loop now only counts as
clicking if its wrap also breaks the curve of the waveform, which is the measure of the snapping.

* Snap loops which click: a loop which does not jump is not moved to a position which jumps

The snapping measures the bend of the waveform at the loop point, and a loop whose last frame
repeats its first frame bends a little there. The nearest zero-crossings of a pulse wave can be
half of its level apart, so such a seamless loop was moved to a position which jumps at every
repeat. A position which jumps at the loop point is not taken anymore for a loop which does not
jump there, measured like the note about clicking loops measures the jump.

---------

Co-authored-by: Jürgen Moßgraber <juergen.mossgraber@googlemail.com>
AIFC files whose sound data is 32-bit floating-point ('fl32') were rejected
as if they were compressed, e.g. the sample 'Clave Synth' of the Core Library
of Ableton Live, which its rack 'Percussion Spirit Kit' plays, and the spring
impulse response of Ableton's 'Convolution Reverb' pack. The data is plain
big-endian IEEE float; it is now converted from the chunks like the other
AIFC files, into a WAV file with IEEE float data.

The converted audio is identical, sample for sample, to the source and to
what macOS decodes from the same files. The 325 integer AIFF and AIFC samples
which the Sample Files source converts from the multi-samples of the Core
Library are written byte-identically as before.
@douglas-carmichael

Copy link
Copy Markdown
Contributor Author

GitHub closed this PR automatically when main was force-pushed to the rewritten history and does not allow reopening it. The same commits, rebased onto the new main, are in #487.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants