# HSC Music Format *(HSC AdLib Composer / HSC Tracker, OPL2 Music Data Format)* --- ## Overview The **HSC file format** stores music sequences composed for **AdLib (OPL2)** synthesizers. It contains 128 instruments, a pattern order list, and pattern data (note and effect pairs). Each HSC file represents a complete song that can be played by a 9-channel OPL2 synthesizer. | Section | Description | Size | | --------------- | ---------------------------------------------- | ----------------- | | Instrument bank | 128 instruments × 12 bytes | 1536 bytes | | Order list | Sequence of pattern indices (51 bytes typical) | 51 bytes | | Pattern data | 64 rows × 9 channels × 2 bytes per pattern | 1152 × N patterns | --- ## File Layout Summary | Offset | Size | Description | | -----: | ---- | --------------------------------------------------------------- | | 0x0000 | 1536 | Instrument data (128 × 12 bytes) | | 0x0600 | 51 | Order list (pattern sequence) | | 0x0633 | ... | Pattern data (each 1152 bytes = 64 rows × 9 channels × 2 bytes) | The number of patterns is derived as: ``` pattern_count = (file_size - 1587) / 1152 ``` --- ## 1. Instrument Block (1536 bytes) Each instrument is **12 bytes** and defines all OPL2 register parameters for one sound. | Byte | Register Written | Description | | ---: | ---------------------- | ----------------------------------------------------- | | 0 | 0x23 + op | Carrier: Tremolo/Vibrato/Sustain/KSR + Multiple | | 1 | 0x20 + op | Modulator: Tremolo/Vibrato/Sustain/KSR + Multiple | | 2 | 0x43 + op | Carrier Total Level (TL) / KSL — bit corrected | | 3 | 0x40 + op | Modulator Total Level (TL) / KSL — bit corrected | | 4 | 0x63 + op | Carrier Attack/Decay | | 5 | 0x60 + op | Modulator Attack/Decay | | 6 | 0x83 + op | Carrier Sustain/Release | | 7 | 0x80 + op | Modulator Sustain/Release | | 8 | 0xC0 + chan | Feedback + Connection | | 9 | 0xE3 + op | Carrier waveform select | | 10 | 0xE0 + op | Modulator waveform select | | 11 | (not written directly) | Pitch slide base (upper nibble, normalized) | A nibble is a group of 4 bits — exactly half a byte. ### Bit Correction Details (`ins[2]`, `ins[3]`, `ins[11]`) The original **HSC instrument format** (as produced by early HSC Tracker/AdLib Composer versions) stored certain **OPL2 register fields** in a slightly inconsistent way compared to the actual **AdLib chip layout**. When AdPlug (and compatible players) load instruments, they apply the following fix-ups: ```cpp ins[2] ^= (ins[2] & 0x40) << 1 // adjust carrier TL/KSL bit overlap ins[3] ^= (ins[3] & 0x40) << 1 // adjust modulator TL/KSL bit overlap ins[11] >>= 4 // normalize pitch slide nibble ``` #### 1. KSL/TL Bit Fix (`ins[2]` and `ins[3]`) * Each operator in the OPL2 has a **Total Level (TL)** register: ``` Bits 0–5 → Volume level (0–63) Bits 6–7 → Key Scale Level (KSL) ``` * In some HSC instrument banks, the **bit 6 (value 0x40)** — the lowest KSL bit — was **misaligned** due to the tracker’s internal byte packing. This caused the TL and KSL fields to overlap incorrectly: e.g., a KSL value of 1 might double the intended TL level. * The expression ```cpp ins[x] ^= (ins[x] & 0x40) << 1 ``` effectively **swaps** the misplaced KSL bit into its proper position (bit 7). In other words, if bit 6 was set, bit 7 becomes set too, restoring the correct KSL value. * This ensures the written OPL registers (`0x40+op` and `0x43+op`) receive the right 2-bit KSL field and a clean 6-bit TL value. #### 2. Pitch Slide Normalization (`ins[11]`) * Byte 11 of each instrument stores a **base frequency offset**, used by the tracker’s pitch-slide effect. * The original HSC files encoded this offset in the **upper nibble** (bits 4–7) only. For example, an instrument might have `ins[11] = 0xA0`, meaning a slide base of `0x0A`. * AdPlug shifts it down with: ```cpp ins[11] >>= 4 ``` so the value becomes a normal 4-bit integer (0–15) that can be directly added to the F-number during note playback. --- **Summary:** These adjustments correct for historical quirks in how the tracker saved instrument bytes. They ensure that the reconstructed OPL register values match what the composer actually heard when saving the `.HSC` file — preserving tone and pitch consistency across players. --- ## 2. Order List (Song Sequence) | Description | Notes | | ---------------------------- | -------------------------------------------- | | Contains up to **51 bytes** | Each byte = pattern index | | `0xFF` | End of song | | `0x80..0xB1` | Goto pattern command (lower 7 bits = target) | | `>= 0xB2` | Invalid / treated as song end | | Out-of-range pattern indices | Clamped to valid count (≤ 50) | Pseudo-code to read: ```text orders = read(51) for each entry: if entry == 0xFF → end_of_song else if entry >= 0x80 and entry <= 0xB1 → jump to entry & 0x7F else if entry >= 0xB2 → treat as 0xFF ``` --- ## 3. Pattern Data Each pattern = **64 rows × 9 channels**, where each cell is 2 bytes: | Byte | Name | Description | | ---: | -------- | -------------------------------- | | 1 | `note` | Note number or instrument marker | | 2 | `effect` | Effect or instrument number | ### Pattern Size: ``` 64 rows × 9 channels × 2 bytes = 1152 bytes ``` ### Note Cell Meaning | Condition | Action | | ------------------------ | ----------------------------------------------------- | | `note == 0` | No note change (effects may still apply) | | `note & 0x80 != 0` | Set instrument → `effect = instrument number` | | `note != 0` | Play note: AdPlug decrements value by 1 before lookup | | Resulting `note == 0x7E` | Key-off (pause) | --- ## 4. Effects (by High Nibble of Effect Byte) | High Nibble | Description | Behavior | | ----------- | ----------------------------- | ----------------------------------------------------------------------------------- | | `0x00` | Global control | 01: Pattern break
03: Fade in
05: 6-voice rhythm ON
06: 9-voice melodic ON | | `0x10` | Pitch slide down | Decrease frequency by low nibble | | `0x20` | Pitch slide up | Increase frequency by low nibble | | `0x50` | Set percussion instrument | (Unused) | | `0x60` | Set feedback | `C0+chan = (ins[8]&1) + (low_nibble<<1)` | | `0xA0` | Set carrier volume | TL of carrier = `(low_nibble << 2)` | | `0xB0` | Set modulator volume | TL of modulator = `(low_nibble << 2)` | | `0xC0` | Set overall instrument volume | Both ops’ TL set to `(low_nibble << 2)` | | `0xD0` | Position jump | Jump to order `low_nibble` | | `0xF0` | Set speed | Speed = `low_nibble` | --- ## 5. Timing & Playback | Parameter | Description | | --------------------- | ---------------------------------------------- | | Refresh rate | 18.2 Hz (one tick per PIT IRQ0 interval) | | Speed | Rows per tick counter (`speed`, `del`) | | Speed effect (`F0xx`) | Sets `speed = value`, `del = speed + 1` | | Rows per pattern | 64 | | Channels | 9 total, or 6 melodic + 3 drums in rhythm mode | Pseudo-logic per update tick: ```text if (--del == 0): del = speed advance row else: process effects only ``` --- ## 6. Rhythm (6-Voice) Mode Enabled by effect `05`. Channels 0–5 remain melodic, while channels 6–8 become drums: | Channel | Drum | Bit in 0xBD | | ------- | --------- | ----------- | | 6 | Bass Drum | Bits 4–5 | | 7 | Hi-Hat | Bit 0 | | 8 | Cymbal | Bit 1 | When rhythm mode is active, note events on channels 6–8 toggle these bits instead of normal note-on. --- ## 7. End-of-Song Behavior When the order list hits `0xFF` or an invalid pattern index: ``` songend = true reset songpos to 0 (or stop playback) ``` If a pattern break (`01` effect) occurs, the player skips remaining rows in the current pattern and proceeds to the next order. --- ## 8. Suggested Writer Implementation When producing `.HSC` files programmatically: 1. **Write 128 instruments** — 12 bytes each (zeros allowed if not defined). 2. **Write order list (51 bytes)** — pattern numbers, ending with `0xFF`. 3. **Write patterns** — each 64×9×2 bytes. 4. **Encode cells:** * Set instrument: `note |= 0x80`, `effect = instrument_index`. * Play note: `note = pitch_number`, `effect = effect_byte`. * Empty cell: `note = 0`, `effect = 0`. 5. **Initial speed:** place an `F0xx` effect on first row (typ. `F006`). 6. **Enable drums:** issue `05` (global) in a control channel. --- ## 9. Practical Limits | Property | Limit | | ---------------- | ----- | | Instruments | 128 | | Patterns | 50 | | Orders | 51 | | Channels | 9 | | Rows per pattern | 64 | --- ## 10. Example Pseudocode: Pattern Decoding ```pseudocode for each row in 64: for each chan in 9: note = read_byte() eff = read_byte() if note & 0x80: instrument[chan] = eff continue if note == 0: apply_effect(chan, eff) continue note = note - 1 if note == 0x7E: key_off(chan) continue pitch = note_table[note % 12] + instrument[chan].pitchbase + slide[chan] octave = ((note / 12) & 7) << 2 set_frequency(chan, pitch, octave) apply_effect(chan, eff) ``` --- ## 11. Implementation Notes * The **fixed tick rate** (18.2 Hz) matches the PC timer interrupt frequency. * **Speed** defines how many ticks per row; lower values = faster playback. * **Instrument TL/KSL** correction ensures compatibility with older HSC data banks. * Pattern arrays are capped at 50; exceeding this may cause truncation. --- ## 12. Reference Specification derived from analysis of AdPlug’s `hsc.h` and `hsc.cpp` (licensed open source) but rewritten as an **independent, formal description** for implementers of HSC readers/writers.