Skip to content
Merged
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
3 changes: 3 additions & 0 deletions documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
* Roland MV-8000
* Fixed: A written patch loaded on the device but was silent, and the device showed a start point, loop start and end point of 0 for every sample. The hardware plays - and displays - the three points which are stored in the SMT slot of a partial, not the ones of the sample parameter block, and only the latter were written. The slot points are written now; the 103 factory patches carry identical values in both places in all of their 1918 slots. Reading prefers the slot points as well, so a patch whose points were edited on the device converts with the points it actually plays.
* Fixed: The wave data of a written sample now keeps 2 frames behind its end point, as the device does for every sample it writes itself - none of the 1918 samples of the factory patches ends closer to the end of its data. A sample whose end point sat at the last frame of its data (a one-shot which plays to its very end, e.g. every sample of an Akai S1000 program whose end marker is its last frame) is padded with 2 frames of silence.
* Fixed: The envelope times are now converted with the time table of the sound engine instead of a formula borrowed from the S-7xx series. Every time is a 0-127 setting which the engine looks up in a 128 entry table, and that table sits in the firmware of both machines - at offset 0xB63EE of the S-760 system disk and 0x50538C of the decompressed MV-8000 operating system, with all 127 overlapping entries identical, which is also what shows that the MV-8000 runs the S-7xx sound engine and not the XV one whose category list it borrows. Its unit follows from the S-760 owner's manual, which documents the LFO delay - a 0-127 setting like the envelope times - as covering "0.01 - 22 sec": the table runs 30 to 65535, so an entry is 1/3000 second and the range is 0.0100 to 21.845 seconds, matching both ends of the documented range within one percent. The formula used before spans only 64:1 and cannot express anything below 302 ms, so it was up to 29 times too slow where the music is - a setting of 1 is 0.011 s and not 0.313 s, and 15, the median of the factory patches, is 0.025 s and not 0.496 s - and agreed only near the top of the range. The electric piano of the factory patch 'Mark V' now attacks in 0.011 s and releases in 0.016 s, as an electric piano does, instead of taking 0.3 s to do either.
* Roland S-7xx
* Fixed: The envelope times are now converted with the same table, which is the sampler's own; the formula used before was an uncalibrated approximation of it with the same 29-fold error at the fast end.
* Teenage Engineering OP-XY
* Fixed: The samples which do not make it into the written preset - the dropped velocity layers and the regions beyond the maximum of 24 - were still copied into the preset folder, although the description file does not reference them. They only occupied space on the device and could make the preset folder too big to load. Only the samples of the written regions are stored now.
* Waldorf Quantum/Iridium
Expand Down
2 changes: 1 addition & 1 deletion documentation/README-FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ The following limitations apply:

The Roland MV-8000 Production Studio (2003) and its successor MV-8800 are pad-based sampling workstations. A patch consists of up to 96 partials which are assigned to the 96 pads (6 pad banks with 16 pads), each partial layers up to 4 samples (SMT slots) with velocity ranges and crossfades. Stereo samples are stored as 2 mono samples. Patches are stored in single *.mv0* files which contain all parameters and the sample data (16-bit/44.1kHz).

The file format is not documented by Roland, it was reverse-engineered from the factory patches and the parameter tables of the MV-8000/MV-8800 firmware (see *documentation/design/MV8000_FORMAT.md*). Names, the category, the note mapping, velocity ranges and crossfades, loops, play modes, pitch key-follow, SMT level/panning/coarse/fine tuning, the amplitude envelope, the filter (type, cutoff, resonance, envelope) and the sample data are read and written. The hardware curve of the envelope times is unknown, times are approximated with the curve of the S-7xx series. Note that MV-8800 *.mvf* files are effect presets (e.g. for the Analog Bass Synth) and not patches, they cannot be converted.
The file format is not documented by Roland, it was reverse-engineered from the factory patches and the parameter tables of the MV-8000/MV-8800 firmware (see *documentation/design/MV8000_FORMAT.md*). Names, the category, the note mapping, velocity ranges and crossfades, loops, play modes, pitch key-follow, SMT level/panning/coarse/fine tuning, the amplitude envelope, the filter (type, cutoff, resonance, envelope) and the sample data are read and written. The envelope times are converted with the time table of the sound engine, which the MV-8000 inherits from the S-7xx series: 0.01 to 21.8 seconds over the 0-127 setting. Note that MV-8800 *.mvf* files are effect presets (e.g. for the Analog Bass Synth) and not patches, they cannot be converted.

When writing patches, samples are converted to 16-bit/44.1kHz. Since the note range of a patch is limited to MIDI notes 21-116, zones outside of this range are clipped or skipped. Identical samples mapped to multiple key ranges are stored only once.

Expand Down
79 changes: 51 additions & 28 deletions documentation/design/MV8000_FORMAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,36 +191,59 @@ sample IDs), 0xE0 = 32-bit region, 0xF8 = value descriptor of an 8-bit cell,
offset 0x5372C4 (patch table at 0x536E00). The "Init Partial" record of the factory
patches matches the table defaults exactly.

## Envelope and LFO times

Every time is a 0-127 setting which the sound engine looks up in a **128 entry table**.
The table was read out of two firmwares, which hold the identical value sequence:

| firmware | offset | layout |
|----------|--------|--------|
| S-760 system disk, version 2.24 (`S760224.OUT`) | `0xB63EE` | u16 little-endian, descending |
| MV-8000 OS 3.54, decompressed image | `0x50538C` | u16 big-endian, ascending |

All 127 overlapping entries match, which is also the evidence that the MV-8000 runs the
S-7xx sound engine rather than the XV one whose category list it borrows (two further
tables are shared - a 2:1 pitch/frequency ratio ramp and a linear ramp - and the S-760
system disk identifies itself as `S770 MR25A`). The table runs 30 to 65535 as a
geometric series with a ratio of 1.0624 per step, spanning 2184:1.

**The unit is 1/3000 of a second.** The S-760 owner's manual documents the LFO delay -
a 0-127 setting like the envelope times - as covering "0.01 - 22 sec". 0.01 s / 30 gives
1/3000 s per entry, and the last entry is then 65535/3000 = 21.8 s, so both ends of the
documented range are matched within one percent:

t(setting) = TABLE[setting] / 3000 seconds ~ 0.01 * 2^(setting/11.45)
= 0.0100 s at 0 .. 21.845 s at 127

The formula used before - `20 * 2^((v-127)/21)`, borrowed from the S-7xx with no
calibration - spans only 64:1 and cannot express anything below 302 ms, so it was up to
29 times too slow at the fast end (setting 1 is 0.011 s and not 0.313 s; setting 15, the
median of the factory patches, is 0.025 s and not 0.496 s) and agreed only near the top.

### What is inference rather than proof

The manual documents that range for the LFO *delay*, and the code which indexes the
table has not been located, so that the envelope times use the same table is not shown
directly. What supports it: it is the only long-span geometric time table in the S-760
(the other two span 131:1 and 102:1 over 5-720 ms), and the ZEN-Core envelope law which
ConvertWithMoss measured on FANTOM-0 hardware spans 0.010 s to 21.5 s, agreeing at both
ends within about 1.5 %. Recording an MV-8000 or MV-8800 settles it: a patch which maps
one looped sine to a row of pads whose only difference is the time under test gives the
law directly.

Finding the indexing code in the S-760 firmware is blocked on its memory banking. Its CPU
is an Intel **S80C196KB** (MCS-96, 16 bit, little-endian; block diagram page 4 and parts
list page 5 of the service notes) whose 64 KB address space is expanded to 1 MB by the
`HG62E33B0B` gate array (`ABUS0`-`ABUS19`), with 1 MB of program DRAM (`IC68`/`IC69`)
holding the whole disk image. An overlay mapping can be recovered exactly wherever a
computed goto appears - `add Rn,#imm; br [Rn]` places the branch table at a known file
offset, so K = offset - imm, which gives K = `0x8400` for file `0xA5DB`-`0xF100` - but
under every recovered mapping the table lies outside the 16 bit range, and which window
the gate array maps is internal to that custom part.

## Not decoded / open

- The hardware curve for envelope times (0-127 → seconds) and the LFO rate table.
The parameter block carries no unit: the partial descriptor table of MV-8000 OS
3.54 declares the four TVA and TVF times as plain 0-127 fields (defaults
0/10/10/10, min 0, max 127) and no code was found which converts them, so the
engine program `MIAMI.PRG` - loaded separately by the boot loader, never
published by Roland - is what interprets them.

What *is* in the OS is a **128 entry exponential table** at file offset 0x50538C
of the decompressed image (u16 big-endian, 30 → 65535, a constant ratio of
1.0624 per step, 11.45 steps per doubling, spanning 2184:1). The same value
sequence sits in the S-760 system disk (Roland's own "S-760 System Version 2.24"
download, `S760224.OUT` at offset 0xB63EE, u16 little-endian, stored descending):
all 127 overlapping entries are identical. Two further tables are shared - a 2:1
ramp which is a pitch/frequency ratio table and a linear normalisation ramp - and
the S-760 disk identifies itself as `S770 MR25A`, so the MV-8000 inherits the
S-7xx sound engine rather than the XV one whose category list it borrows.

A 128 entry table indexed 0-127 with that span is the natural candidate for the
envelope time law, and its range corroborates independently: the ZEN-Core law
which ConvertWithMoss measured on a FANTOM-0 spans 2150:1 (0.010 s to 21.5 s).
It is **not confirmed** - no code which indexes the table has been located in
either firmware, so the tick it counts, and with it the absolute times, are still
unknown. What is certain is that the S-7xx formula used today, `20 *
2^((v-127)/21)`, spans only 64:1 and cannot represent anything below 302 ms,
which no calibrated Roland law does.

Recording the hardware settles it: a patch which maps one looped sine to a row of
pads whose only difference is the envelope time under test gives the law directly.
- The LFO rate table (the LFO rate is a 0-149 setting, 128 and above being tempo-sync).
- The exact roles of the ±63 sensitivity params around the TVF/TVA envelopes, and the
unit of the 8-bit sub-frame parts of the slot playback points (1/256 frame?).
- Patch common bits 155-416 beyond category/level/pan/mute-group/tuning (contains at
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Written by Jürgen Moßgraber - mossgrabers.de
// (c) 2019-2026
// Licensed under LGPLv3 - http://www.gnu.org/licenses/lgpl-3.0.txt

package de.mossgrabers.convertwithmoss.format.roland;

/**
* The time table of the Roland S-7xx sound engine, which the MV-8000/MV-8800 inherits.
*
* The samplers store every time as a 0-127 setting and look up the actual duration in a 128 entry
* table. The table was read out of two firmwares, which hold the identical value sequence: the
* S-760 system disk (version 2.24, file offset 0xB63EE, 16 bit little-endian, stored descending)
* and the decompressed MV-8000 operating system (version 3.54, offset 0x50538C, 16 bit big-endian,
* ascending). All 127 overlapping entries match.
*
* The unit of the entries is 1/3000 of a second. The S-760 owner's manual documents the LFO delay,
* a 0-127 setting like the envelope times, as covering "0.01 - 22 sec": the table runs from 30 to
* 65535, so 0.01 s / 30 gives 1/3000 s per entry and the last entry is 65535/3000 = 21.8 s, which
* matches both ends of the documented range within one percent. The resulting law spans 0.0100 s
* to 21.845 s in a geometric series with a ratio of 1.0624 per step, or 0.01 * 2^(value/11.45).
*
* @author Jürgen Moßgraber
*/
public class RolandTimeTable
{
/** The number of table entries which pass in one second. */
private static final double TICKS_PER_SECOND = 3000.0;

/** The time table of the sound engine, in units of 1/3000 second. */
private static final int [] TIMES =
{
30,
32,
34,
36,
38,
41,
43,
46,
49,
52,
55,
58,
62,
66,
70,
74,
79,
84,
89,
95,
101,
107,
114,
121,
128,
136,
145,
154,
163,
174,
184,
196,
208,
221,
235,
250,
265,
282,
299,
318,
338,
359,
381,
405,
431,
457,
486,
516,
549,
583,
619,
658,
699,
743,
789,
838,
890,
946,
1005,
1068,
1134,
1205,
1280,
1360,
1445,
1535,
1631,
1733,
1841,
1956,
2078,
2208,
2346,
2492,
2648,
2813,
2989,
3175,
3373,
3584,
3808,
4045,
4298,
4566,
4851,
5154,
5475,
5817,
6180,
6566,
6976,
7411,
7874,
8365,
8887,
9442,
10031,
10657,
11323,
12029,
12780,
13578,
14425,
15326,
16282,
17298,
18378,
19525,
20744,
22038,
23414,
24875,
26428,
28077,
29830,
31692,
33670,
35771,
38004,
40376,
42896,
45573,
48418,
51440,
54650,
58061,
61685,
65535
};


/**
* Private due to helper class.
*/
private RolandTimeTable ()
{
// Intentionally empty
}


/**
* Get the number of settings of the table.
*
* @return The number of settings
*/
public static int getNumberOfValues ()
{
return TIMES.length;
}


/**
* Convert a time setting of the sampler into seconds.
*
* @param value The setting in the range of 0-127
* @return The time in seconds, 0.01 at the lowest setting and 21.845 at the highest
*/
public static double valueToSeconds (final int value)
{
return TIMES[Math.clamp (value, 0, TIMES.length - 1)] / TICKS_PER_SECOND;
}


/**
* Convert a time in seconds into the time setting of the sampler. Since the table is a
* geometric series the nearest setting is the one with the smallest ratio to the given time,
* not the one with the smallest difference.
*
* @param seconds The time in seconds
* @return The setting in the range of 0-127
*/
public static int secondsToValue (final double seconds)
{
final double ticks = seconds * TICKS_PER_SECOND;
if (ticks <= TIMES[0])
return 0;
final int last = TIMES.length - 1;
if (ticks >= TIMES[last])
return last;
for (int i = 1; i <= last; i++)
if (ticks < TIMES[i])
return ticks * ticks < (double) TIMES[i - 1] * TIMES[i] ? i - 1 : i;
return last;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import de.mossgrabers.convertwithmoss.file.AudioFileUtils;
import de.mossgrabers.convertwithmoss.file.wav.WaveFile;
import de.mossgrabers.convertwithmoss.format.TagDetector;
import de.mossgrabers.convertwithmoss.format.roland.RolandTimeTable;
import de.mossgrabers.tools.FileUtils;
import de.mossgrabers.tools.StringUtils;

Expand Down Expand Up @@ -419,17 +420,15 @@ private static void writeFilter (final MV8000Partial partial, final ISampleZone


/**
* Convert a time in seconds to an envelope time value. Inverse of the read formula (S-7xx
* lineage approximation, up to 20 seconds).
* Convert a time in seconds to an envelope time value with the time table of the sound engine
* (see {@link RolandTimeTable}).
*
* @param seconds The time in seconds
* @return The time value in the range of 0..127
*/
private static int calculateTimeValue (final double seconds)
{
if (seconds <= 0)
return 0;
return Math.clamp ((int) Math.round (127 + 21 * Math.log (seconds / 20.0) / Math.log (2)), 0, 127);
return RolandTimeTable.secondsToValue (seconds);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import de.mossgrabers.convertwithmoss.core.model.implementation.InMemorySampleData;
import de.mossgrabers.convertwithmoss.core.settings.MetadataSettingsUI;
import de.mossgrabers.convertwithmoss.format.TagDetector;
import de.mossgrabers.convertwithmoss.format.roland.RolandTimeTable;
import de.mossgrabers.tools.FileUtils;


Expand Down Expand Up @@ -346,15 +347,15 @@ private static void createFilter (final ISampleZone zone, final MV8000Partial pa


/**
* Convert an envelope time value to seconds. The exact hardware curve is unknown, the formula
* of the S-7xx series (same lineage, up to 20 seconds) is used as an approximation.
* Convert an envelope time value to seconds with the time table of the sound engine, which the
* MV-8000 inherits from the S-7xx series (see {@link RolandTimeTable}).
*
* @param value The time value in the range of 0..127
* @return The time in seconds
*/
private static double calculateTime (final int value)
{
return value == 0 ? 0 : 20.0 * Math.pow (2.0, (value - 127.0) / 21.0);
return RolandTimeTable.valueToSeconds (value);
}


Expand Down
Loading