What does this page do?

Takes a disc image file of Sly 2 or Sly 3 (including some of the demos), and lets you listen to (and download, raw or rendered to wav) all of its streamed music and ambient tracks.

Doesn't it take long to upload a whole disc image?

No because you're not uploading anything anywhere. This page does all the work locally, in your browser (preferably Firefox), without relying on a server (except for loading this page and its scripts).
It's a bit like vgmstream's web player, but in pure javascript.

Why does this page only work with some discs?

Because this page is made for a specific format used in Sly 2 and 3, and I had to manually find and classify each track. These games in particular have their entire file structure hidden, so searching for them is not as easy as opening a folder and reading a name.

Can I play the raw files that I get from this page in vgmstream?

Yes if you put this file in the same directory and rename it ".kpv.txth" (dots are important).
Read more about vgmstream's TXTH files on their github



So what does the page do exactly? (for programmers)

  • Given an .iso file, reads its ID code
      (slyDiscMusic_io.js, function discIdPromise).
  • If the ID is recognized
      (if it appears in the table knownIDs at the end of slyDiscMusic_data.js),
      shows you a list of all known tracks from that disc, and a few filter options
  • When you select a track, it starts playing
      (slyDiscMusic_play.js, function playTrack)
      using the AudioWorklet defined in psxadpcm.js
  • While a track is playing, you can see some information about the track in a table,
      download the track in its original raw format (tentatively named ".kpv")
      or render it to wav (slyDiscMusic_wav.js, function renderAdpcmToWav).
    Like above, any downloaded files are created on the spot by your browser (preferably Firefox).
  • If the track has several layers, you can switch between them during playback using the slider under the selection list.

Can this page recognize other files?

Yes (see slyDiscMusic_io.js, function loadFileDataPromise)

  • If you open a file starting with " KPV" (like the results of the Download Raw File button), the page will play them.
  • If you open a .bin image, the page will explain why it isn't supported directly, but let you convert it to .iso

If I don't want to excavate it from your code, what's the format of these files?

  • Header: 0x800 bytes (1 entire disc sector) (see interpretTrackHeader in slyDiscMusic_play.js):
    Offset Format Value Notes
    0x000 ascii string " KPV" (0x20 0x4B 0x50 0x56)
    0x004 Uint32 LE length: number of used bytes per channel each channel occupies an integer number of 0x800 sectors, so this value should be rounded up to get the actual file size
    0x008 Uint32 LE Unknown (sector size?), always 0x800
    0x00C Uint32 LE interleave*2: double of channel interleave Usually 0x2000, meaning that channels alternate every 0x1000 bytes.
    0x010 Uint32 LE sampleRate
    0x014 Uint32 LE channels
    0x018 byte⁠[channels]⁠[4] channel mapping see below
    0x7FC Uint32 LE loop: byte offset of loop start point in each channel
  • Channel mapping for each channel:
    Byte 0 Byte 1 Uint16 LE
    0x7F Layer number 0x0000 for Mono layers,
    0x005A and 0x010E to couple 2 channels into a Stereo layer
  • Audio data: PSX ADPCM (decoding function psxApcmDecoder in streamGen.js, based on vgmstream implementation)
    Each row of 16 bytes decodes to 28 samples.
    Byte 0 Byte 1 Bytes 2...15
    Low Nibble: shift
    High Nibble: coef
    Flags
    (unused)
    28 signed nibbles (low before high): minisamples

    new sample =  minisample<<(12-shift)
      + prev[0]*coefTable[0][coef]
      + prev[1]*coefTable[1][coef]
    where "coefTable" is the same as the one given in vgmstream, "prev[n]" are the previous output samples that you must keep track of when decoding.
    Each disc sector (size 0x800 bytes) contains 128 rows = 224 samples;
    The final sector of each channel is padded to the end with rows of silence (0xC0 00 00 00 ...).