Tabula Sonora 0.1.0
A native C++20 implementation of the Roland Sound Canvas VA synth voice
Loading...
Searching...
No Matches
ts::DrumKitTable Class Reference

The drum kit records: for each of 128 keys, which tone sounds and how it is levelled, tuned, panned and grouped. More...

#include <tabulasonora/drum_kit_table.hpp>

Public Member Functions

 DrumKitTable (const RomImage &rom)
 Creates the table by reading the drum regions out of the ROM image.
int kit_count () const noexcept
 Number of kit records reachable through the program map.
int map_row (int internal_bank) const noexcept
 Maps an internal bank code to a drum map row; standard GM/GS drum parts use 0x04.
std::optional< int > kit_for_program (int program, int row=0) const noexcept
 Resolves a drum program to its kit record index, or nothing for an undefined program.
std::string kit_name (int kit) const
 The kit's name, as the ROM record carries it, trimmed of trailing spaces.
DrumKey key (int note, int kit=0) const
 Reads one key's settings from a kit; kit 0 is GM Standard.

Static Public Member Functions

static std::optional< int > row_for_map (ToneMap map) noexcept
 The drum map row a vintage's tone map selects, or nothing where no measurement pins one.
static double coarse_pitch_ratio (int pitch) noexcept
 The playback ratio the kit's coarse-pitch plane implies.
static constexpr double level_gain (int level) noexcept
 The gain the kit level contributes, as amplitude.

Static Public Attributes

static constexpr int kit_stride = 0x50C
 Bytes per kit record.
static constexpr int key_count = 128
 Keys per kit.
static constexpr int map_row_count = 6
 Rows in the drum program map.
static constexpr std::int64_t kit_index_offset = 0x19F21B0
 File offset of the table mapping a level-two index to a kit record index.

Detailed Description

The drum kit records: for each of 128 keys, which tone sounds and how it is levelled, tuned, panned and grouped.

Three things about drums are easy to get wrong, and all three are settled by measurement:

  • Drum sounds are ordinary melodic tones. They do not use the separate drum tone table (stride 0x1e8), which is why that table's remaining unreversed state does not block GM kits.
  • They do not resolve through the three-level melodic lookup; a program change on the drum part selects a kit through its own pair of lookups.
  • The note does not transpose the sample. The kit's coarse-pitch plane supplies the key instead, pivoting on 60, and the tone's own key-follow decides what a step of it is worth.

Constructor & Destructor Documentation

◆ DrumKitTable()

ts::DrumKitTable::DrumKitTable ( const RomImage & rom)
explicit

Creates the table by reading the drum regions out of the ROM image.

Member Function Documentation

◆ kit_count()

int ts::DrumKitTable::kit_count ( ) const
inlinenodiscardnoexcept

Number of kit records reachable through the program map.

◆ row_for_map()

std::optional< int > ts::DrumKitTable::row_for_map ( ToneMap map)
staticnodiscardnoexcept

The drum map row a vintage's tone map selects, or nothing where no measurement pins one.

The module takes this from the part's internal bank code, and that translation is not reversed — but which row each vintage ends up on is observable. Driving the DLL with each tone map in turn and reading back the tone it resolves for program 0 on the drum part: SC-55 selects row 3 (kit 59), SC-88 row 2 (kit 47), SC-88Pro row 1 and SC-8820 row 0.

Rows 4 and 5 are not vintages: row 4 is the XG kit set — Standard 1/2, Room, Rock, Electro, Analog, Jazz, Brush, Classic on programs 0/1/8/16/24/25/32/40/48, plus SFX 1 and 2 on 120 and 121 — and row 5 is GM2's. ToneMap::xg selects row 4; a host that wants row 5 sets it itself.

◆ map_row()

int ts::DrumKitTable::map_row ( int internal_bank) const
nodiscardnoexcept

Maps an internal bank code to a drum map row; standard GM/GS drum parts use 0x04.

◆ kit_for_program()

std::optional< int > ts::DrumKitTable::kit_for_program ( int program,
int row = 0 ) const
nodiscardnoexcept

Resolves a drum program to its kit record index, or nothing for an undefined program.

The engine leaves the kit unchanged rather than silencing the part, which is why this is an absence rather than a zero.

◆ kit_name()

std::string ts::DrumKitTable::kit_name ( int kit) const
nodiscard

The kit's name, as the ROM record carries it, trimmed of trailing spaces.

Twelve bytes at +0x500 of the record, the same field the module's rhythm-set name dump reads. A mixer that shows "kit 73" is showing an index nobody chose; the record has been carrying "Analog Kit" all along.

Three things not to assume about the result, all measured and written up in FINDINGS under "Kit names, and three things not to assume about them":

  • The casing is the ROM's and is not normalised here. Every GS and GM2 kit is upper case and every XG kit is lower, which makes an ALL-CAPS name on XG-flavoured material a free signal that the drum row is not following XG mode.
  • A name is not an identifier. Fifteen are shared by two to four records — ROOM is four, one per GS vintage — so anything keyed on the name collides across exactly the vintages a tone map exists to separate.
  • A twelve-byte name may be abbreviated, and not always at the end: standrd kit2 drops the a of "standard", GM2 ORCHSTRA the E of "ORCHESTRA".

Empty for a kit index outside the table.

◆ key()

DrumKey ts::DrumKitTable::key ( int note,
int kit = 0 ) const
nodiscard

Reads one key's settings from a kit; kit 0 is GM Standard.

Throws std::out_of_range if the note is outside 0–127.

◆ coarse_pitch_ratio()

double ts::DrumKitTable::coarse_pitch_ratio ( int pitch)
staticnodiscardnoexcept

The playback ratio the kit's coarse-pitch plane implies.

Only correct for a tone that key-follows at 50%, which is what the SC-88 and SC-8820 standard kits use and what this constant was measured from. The general rule is per-tone: the SC-55 kits and the Electronic family follow at 100% and move a whole semitone per unit, so they come out half as far under this reading. The engine paths use PitchChain::drum_pitch_milli_semitones; this remains for callers that want the old scalar.

◆ level_gain()

constexpr double ts::DrumKitTable::level_gain ( int level)
inlinestaticnodiscardconstexprnoexcept

The gain the kit level contributes, as amplitude.

The kit level is not part of the per-voice amplitude — that matches the engine exactly without it. It enters downstream in the part-volume computation, where the result is squared, so it acts as (level / 127)^2.

Member Data Documentation

◆ kit_stride

int ts::DrumKitTable::kit_stride = 0x50C
staticconstexpr

Bytes per kit record.

◆ key_count

int ts::DrumKitTable::key_count = 128
staticconstexpr

Keys per kit.

◆ map_row_count

int ts::DrumKitTable::map_row_count = 6
staticconstexpr

Rows in the drum program map.

Six, not the two this read for a long time. Each row maps a program to a kit, and program 0 resolves to kit 0, 38, 47, 59, 68 and 79 across rows 0 to 5. Reading two rows put every kit from 47 up out of reach entirely.

Measured against the DLL: driven with the SC-55 tone map on program 0 of the drum part, the engine resolves a tone for all 61 sounding keys that kit 59 reproduces exactly, 61 of 61 — and kit 59 is only reachable through row 3. The best any kit the two-row read could see managed was 30 of 61.

◆ kit_index_offset

std::int64_t ts::DrumKitTable::kit_index_offset = 0x19F21B0
staticconstexpr

File offset of the table mapping a level-two index to a kit record index.

Unlike the other three drum regions this one is not recorded in the manifest, so it is pinned here. It is DAT_1819f31b0 in the decompile.


The documentation for this class was generated from the following file: