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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* New: Added support for the Roland MV-8000/MV-8800 patch format (MV0) (thanks to Douglas Carmichael).
* New: Added support for the Roland ZEN-Core sound format (SVZ) (thanks to Douglas Carmichael).
* New: Added support for the Synclavier Regen timbre/library format (SFLC) (thanks to Douglas Carmichael).
* New: Added support for the Kurzweil K2000/K2500/K2600 format (KRZ, K25, K26) (thanks to Douglas Carmichael).
* New: Added support for the Fairlight CMI 3 - read only (thanks to PythonBlue).
* New: Added support for the Downloadable Sound format (DLS) - read only.
* New: Added support for the Roland ZEN-Core sound format (SVZ). The undocumented container and tone format was reverse-engineered from device exports and the FANTOM/FANTOM-0 firmware (validated against the 2048 factory tones and Roland's own SVZ sample packs). ConvertWithMoss writes an importable .svz - a single tone for one multi-sample, or a multi-tone bank that shares one sample pool for several - loadable through the device's IMPORT function, and each tone carries the source's filter (type, cutoff, resonance) and amplitude envelope. The ZEN-Core voice engine has no loop smoothing of its own, so click-free playback (hardware-verified on a FANTOM-0) is prepared into the samples: they are written at the native 48 kHz / 16-bit, the loop end is re-seated so the wrap reproduces the waveform's own step into the loop start (the seam invariant every Roland-authored loop satisfies), a loop with no seamless end point (an evolving pad) gets its tail cross-faded in phase into the loop-start lead-in, and mono sources are stored mono the way the device's own sampler stores its imports - a looped sample stored as interleaved stereo ticks once per loop pass on the hardware even with a seamless wrap, besides doubling the file and the device's sample RAM. Samples and multisamples are named with a content hash, since the device silently re-uses already imported ones of the same name. A selectable *Target Device* sets the header's model tag for the intended hardware: KY019 (the shared ZEN-Core tag of the FANTOM / FANTOM-0 / FANTOM EX / Juno-X / Jupiter-X/Xm; the default and the device-confirmed case), MI085 (GAIA-2) or RC001 (ZENOLOGY plug-in). The user samples and multisample key map of a .svz are also read back (thanks to Douglas Carmichael).
Expand Down
12 changes: 12 additions & 0 deletions documentation/README-FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,18 @@ Since the format is pretty simple all data stored in the file is available for t

Since the format supports only one group of a multi-sample, multiple destination files are created for each group available in the source. If there is more than one group in the source the name of the created file has the velocity range of the group added. Using that information a multi-sample with up to 4 groups can be created as a Performance in the device.

## Kurzweil K2000/K2500/K2600

The Kurzweil K2000 (1991), K2500 and K2600 samplers/synthesizers share one object file format with the extensions *.krz*, *.k25* and *.k26*. A file is a bank of numbered objects: programs (presets), keymaps (the mapping of sample recordings across the keys and the 8 dynamic velocity levels) and samples, which may contain several recordings (multiple root keys, stereo pairs). The layout was derived from the source code of the GPL tool KurzFiler by Marc Halbruegge (see *documentation/design/KURZWEIL_FORMAT.md*).

When reading, each program becomes one multi-sample: its layers reference keymaps from which the key ranges, velocity levels, root keys, tunings, volume adjusts, loops and the 16-bit sample data are read. Keymaps and samples which are not referenced by any program are converted as multi-samples of their own. Many factory and commercial K-series files map samples from the device ROM which is not present in the file; such zones cannot be converted and are skipped with a note.

When writing, a file is created which uses only K2000 features and therefore loads on all three device families (the current Kurzweil range - K2700, PC4, Forte - imports these files as well). Each multi-sample becomes a program with one layer and a keymap; the velocity layers of the source are quantized onto the 8 dynamic levels of the keymap. One sample object is written per zone (16-bit; the sample rate is kept up to 96kHz, the maximum sample playback rate of the devices) with the zone gain in its volume adjust. Since the device plays a loop until the end of the sample, the data after the loop end is cut off. If any zone is stereo, all samples of the program are written as stereo pairs. The keymap covers MIDI notes 12-127 (C0-G9 in Kurzweil terms), keys below are dropped. Several multi-samples can be written into one file as a library; if the object IDs (200-999 per type) do not suffice, multiple files are created.

#### Destination Options

* Target Device: Selects the device family for which the file is named: *K2000 (krz)*, *K2500 (k25)* or *K2600 (k26)*. Since the written objects use only K2000 features, the selection only sets the file extension.

## Logic EXS24

The Logic EXS24 format is a proprietary sample format used by Logic Pro, a digital audio workstation. It is primarily used for storing and playback of sampled instruments and sounds within Logic Pro. The format allows for comprehensive mapping and editing of samples, as well as providing various modulation and performance options.
Expand Down
Binary file modified documentation/SupportedFeaturesSampleFormats.ods
Binary file not shown.
168 changes: 168 additions & 0 deletions documentation/design/KURZWEIL_FORMAT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Kurzweil K2000/K2500/K2600 File Format (.KRZ / .K25 / .K26)

Object files of the Kurzweil K2000, K2500 and K2600 samplers/synthesizers. All three devices share
one container format; the extension only signals the intended device family. A K2500 reads K2000
files, a K2600 reads both. Program objects may contain device specific segments (K2500 triple
modular processing, KDFX effects, KB3 organ mode) which older devices ignore or reject.

The layout was derived from the source code of the GPL tool *KurzFiler* by Marc Halbruegge
(https://sourceforge.net/projects/kurzfiler/), which reads and writes these files and whose output
loads on the hardware. All multi-byte values are **big-endian**. Sample positions are counted in
16-bit **words**, not bytes.

## File layout

```
offset size
0 4 magic "PRAM"
4 4 int32 objectSize - file offset where the raw sample data region starts
(= end of the object region)
8 24 6 x int32, mostly 0; the 3rd one (offset 16) holds the OS version,
e.g. 353 for 3.53 (written by KurzFiler, value is not essential)
32 ... object blocks (see below) up to 'objectSize'
objectSize raw sample data region: 16-bit big-endian PCM words, referenced by the
sample headers via absolute word indices (byte pos = objectSize + 2 * index)
```

## Object blocks

The object region is a sequence of blocks:

```
int32 blockSize negative: -(4 + paddedObjectLength); a value >= 0 terminates the list
(the terminator conventionally holds the file offset after itself,
i.e. 'objectSize', but readers only test for 'not negative')
object see below, zero-padded to a 4-byte boundary
```

The next block starts at `blockPos + 4 + (-blockSize) - 4 = blockPos - blockSize`.

### Common object header

```
uint16 hash object type and ID: for types <= 42: (type << 10) | id, id = 0..1023
(usable IDs on the device are 1..999)
uint16 size total object size in bytes counted from the start of 'hash' (before padding)
uint16 nameOfs offset from the start of this field to the object data
name ASCII, 0-terminated, padded to even length (nameOfs = 2 + name field length;
max 16 characters on the device)
data size - nameOfs - 4 bytes
```

Object types: 36 = program, 37 = keymap, 38 = sample. (Other types use an 8-bit type in the
hash: 111 = quick-access bank, 112 = song, 113 = effect; these are preserved verbatim but not
interpreted here.)

## Sample object (type 38)

```
int16 baseID (KurzFiler writes 1)
int16 numHeaders number of sample headers - 1
int16 headersOfs offset from this field to the first header (always 8)
uint8 flags 1 = stereo: the headers form left/right pairs (even index = left)
uint8 unused1
int16 copyID
int16 unused2
numHeaders+1 x sample header (32 bytes each):
uint8 rootKey MIDI note of the recorded pitch
uint8 flags 0x80 = loop OFF (inverted!), 0x40 = sample data present in this
file (otherwise the header references device ROM), KurzFiler
writes 0x70 for imported samples
int8 volumeAdjust volume adjust in 0.5 dB steps (-64.0 to +63.5 dB, the range of
the Volume Adjust parameter on the MISC page of the sample
editor in the K2600 manual)
int8 altVolumeAdjust volume adjust used when the alternative start is active
int16 maxPitch highest playable transposition in cents:
ceil(1200*log2(96000e-9*samplePeriod)) + 100*rootKey - 1200
int16 offsetToName
int32 sampleStart absolute word index into the sample data region
int32 altSampleStart alternative start (used when the Alt switch is on)
int32 loopStart absolute word index; the loop always ends at sampleEnd
int32 sampleEnd absolute word index, inclusive (length = end - start + 1)
int16 offsetToEnvelope byte offset from this field to the natural envelope
int16 altOffsetToEnvelope byte offset from this field to the alternative envelope
int32 samplePeriod sample period in nanoseconds (44100 Hz -> 22676)
natural envelope records, 12 bytes each (6 x int16); KurzFiler writes two default records
{-1, 1, 0, 0, -1600, 0} per sample object and points both envelope offsets of every header
at the first one
```

A sample object with several headers is a *multi-root* sample (one recording per root key).
For stereo samples every second header is the right channel of the preceding left one; a
keymap entry references the left header, the right one is implicit.

Non-looped samples: KurzFiler writes the loop flag as ON with `loopStart == sampleEnd`
(a degenerate 1-word loop that holds the last sample value); native one-shot samples have
flag bit 0x80 set instead. Readers should treat `loopStart >= sampleEnd` as "no loop".

## Keymap object (type 37)

```
int16 sampleID sample object ID used for all entries when the method has no
per-entry sample ID (bit 0x02 clear, a "compacted" keymap)
int16 method bit set describing the per-entry fields (in this order):
0x10 tuning as int16 (cents), else 0x08 tuning as int8
0x04 volume adjust as int8
0x02 sample ID as int16
0x01 sub-sample number as uint8
int16 basePitch pitch of entry 0 in cents (0 = MIDI note 12)
int16 centsPerEntry key range covered per entry in cents (normally 100 = 1 semitone)
int16 entriesPerVel number of entries per velocity level - 1 (normally 127)
int16 entrySize bytes per entry (must match the method bits)
int16 level[8] for each of the 8 dynamic levels ppp..fff: byte offset from this
level field to the entry table of the velocity level to use;
writing: (8 - levelIndex) * 2 + tableIndex * tableSize
entry tables numTables x (entriesPerVel+1) x entrySize
```

Entry fields (presence and order by the method bits): `tuning`, `volumeAdjust`, `sampleID`,
`subSampleNumber`. The sub-sample number is the 1-based index of the header inside the sample
object; 0 marks an unused entry. For stereo samples it references the left header (1, 3, 5, ...).

Key mapping: entry `i` responds to MIDI note `12 + (basePitch + i * centsPerEntry) / 100`.
With the standard values (basePitch 0, 100 cents, 128 entries) entry `i` = MIDI note `i + 12`
covering C-1..G9 of the keymap range C0..G10 in Kurzweil terms.

Playback pitch: `recordedPitch + (playedNote - rootKey) * 100 + tuning` cents, i.e. tuning 0
gives normal chromatic tracking of the header's root; a drum map cancels the tracking with
`tuning = 100 * (rootKey - playedNote)`.

The number of distinct entry tables is derived from the level offsets (adjacent equal values
share a table). The 8 dynamic levels map linearly onto MIDI velocity (level j covers
velocities j*16 .. j*16+15).

## Program object (type 36)

A sequence of segments, each a tag byte followed by a fixed-length data block. The object data
ends with a 0 word. Data length by tag: 8 (PGM) and 9 (LYR) = 15 bytes, 15 (FX) = 7 bytes,
otherwise by `tag & 0xF8`: 16/20 (ASR/LFO) = 7, 24 (FUN) = 3, 32 (ENC/ENV/IMP) = 15,
64 (CAL) = 31, 80 (output blocks) = 15, 104 (KDFX) = 7, 120 (KB3) = 31.

Segments used by KurzFiler generated programs (all other bytes 0):

* PGM (tag 8): `[0]` mode (2 = K2000, 3 = K2500, 4 = needs KDFX), `[1]` number of layers,
`[3]` 0x37 bend range, `[4]` 64 portamento
* per layer:
* LYR (tag 9): `[1]` 0x18, `[3]`/`[4]` low/high key, `[5]`/`[6]` 0/0x7F (velocity range),
`[8]` enable flags: 0x04 mono, 0x24 stereo
* ENC (tag 32): all 0 (envelope control, "not natural")
* ENV (tag 33): amplitude envelope, `[1]` = 100, `[7]` = 100
* CAL (tag 64): `[0]` 0x7F, `[1]` keymap transpose, `[3]` 0x2B, `[7..8]` keymap object ID
(big-endian), `[11..12]` second keymap object ID (same as the first for generated
programs), `[29]` 1
* tags 0x50/0x51/0x52: `[0]` = 62/60/60 (output/pan blocks)
* tag 0x53: `[0]` 1, `[2]` 0x70, `[13]` 4, `[14]` 0x90 for stereo panning, 0x00 for mono
```

K2500/K2600 only segments have `(tag & 96) == 96`; converting a program to K2000 strips them
and sets mode 2.

## Not interpreted / unknown

* The unit of the keymap entry volume adjust field is not confirmed and therefore ignored
(the sample header volume adjust is confirmed to be 0.5 dB steps, see above).
* The natural envelope records and the program ENV/ASR/LFO/FUN segment contents beyond the
values above are not decoded. The per-sample playback direction (Normal/Reverse/
Bidirectional on the MISC page of the sample editor) is stored somewhere in the sample
header but the bit positions are unknown.
* Multi-floppy spanning files (.KR1/.K21 etc. part files) are not supported.
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
import de.mossgrabers.convertwithmoss.format.kmp.KMPDetector;
import de.mossgrabers.convertwithmoss.format.korgmultisample.KorgmultisampleCreator;
import de.mossgrabers.convertwithmoss.format.korgmultisample.KorgmultisampleDetector;
import de.mossgrabers.convertwithmoss.format.kurzweil.KurzweilCreator;
import de.mossgrabers.convertwithmoss.format.kurzweil.KurzweilDetector;
import de.mossgrabers.convertwithmoss.format.music1010.bento.BentoCreator;
import de.mossgrabers.convertwithmoss.format.music1010.bento.BentoDetector;
import de.mossgrabers.convertwithmoss.format.music1010.blackbox.Music1010Creator;
Expand Down Expand Up @@ -166,6 +168,7 @@ public ConverterBackend (final INotifier notifier)
new IsoDetector (notifier),
new KMPDetector (notifier),
new KorgmultisampleDetector (notifier),
new KurzweilDetector (notifier),
new EXS24Detector (notifier),
new KontaktDetector (notifier),
new MaschineDetector (notifier),
Expand Down Expand Up @@ -203,6 +206,7 @@ public ConverterBackend (final INotifier notifier)
new TonverkPresetCreator (notifier),
new KMPCreator (notifier),
new KorgmultisampleCreator (notifier),
new KurzweilCreator (notifier),
new EXS24Creator (notifier),
new KontaktCreator (notifier),
new MaschineCreator (notifier),
Expand Down
Loading