├── README.md ├── apm_format_description.txt ├── file_sequences.txt └── ray2get.py /README.md: -------------------------------------------------------------------------------- 1 | # Ray2Get 2.0 2 | 3 | Initially written in C and released back in 2009, Ray2Get is a tool that converts .apm audio files from the PC version of Rayman 2 to .wav files. 4 | 5 | This new version, written in Python for convenience (cross-platform environment, no recompiling required after every tweak), fixes inaccuracies in the decoding process, supports more formats (see *Features* section) and now also allows to convert from .wav to .apm! 6 | 7 | This means that you can replace the game's soundtrack with your own music! But to do so, you will need to split your files in parts and rename them accordingly. The file named `file_sequences.txt` lists all the sequences played in the game at every location (without the `.apm` extensions). 8 | 9 | 10 | ### Inaccurate ADPCM decoding? 11 | 12 | The DLL provided with the Rayman 2 executable (`Rayman2\DLL\APMmxBVR.dll`) doesn't implement the IMA-ADPCM algorithm properly: each time a sample is processed, the ADPCM *step* variable has its least significant 3 bits cleared for some reason. This results in DC offset progressively building up at times when processing files encoded with the regular IMA-ADPCM algorithm (and so are most of the .apm files provided with the game). 13 | 14 | As a workaround, this script provides a variant of the encoding algorithm that cancels out the side effect (by clearing the lowest 3 bits of the *step* in the encoding process as well). The tool uses the "inaccurate" version by default, for both encoding and decoding. Add the `i` parameter to use the standard IMA ADPCM algorithm instead (see *Usage*). 15 | 16 | 17 | ## Features 18 | 19 | * Two algorithms: standard IMA-ADPCM, a Rayman 2-specific variant (see previous section); 20 | 21 | * Now supports any sample rate (although the game will resample to 22050 Hz); 22 | 23 | * Supports mono and stereo files; 24 | 25 | * Supports 8-bit, 16-bit, 24-bit and 32-bit integer WAV as input files (floating point is not supported). The output files are always 16-bit; 26 | 27 | * When decoding, the script discards the first PCM sample (stored in the .apm header) by default (as the game itself does). Use the `d1` option to keep it in the output file. 28 | 29 | 30 | ## Requirements 31 | 32 | The script requires a Python 2.x/3.x environment installed on your machine (it was tested with versions 2.7, 3.2 and 3.4). 33 | 34 | 35 | ## Usage 36 | 37 | `python ray2get.py [options] input_file [output_file]` 38 | 39 | The accepted options are the following: 40 | 41 | * `e`: encode a .wav file to .apm using the in-game algorithm variant; 42 | * `ei`: encode a .wav file to .apm using the standard IMA-ADPCM algorithm; 43 | * `d`: decode an .apm file to .wav using Rayman 2's algorithm (default); 44 | * `di`: decode an .apm file to .wav using the standard IMA-ADPCM algorithm; 45 | * `d1`: decode an .apm file to .wav and keep the first PCM value in the output file; 46 | 47 | For decoding, both `i` and `1` parameters are optional and interchangeable (for example, you may use either `di1` or `d1i` if you want to decode as IMA-ADPCM and keep the first sample). 48 | 49 | The last parameter (output file name) is optional. If not specified, the output file is given the name of the input file with the extension replaced. 50 | 51 | 52 | ### Batch-process all audio files in a directory 53 | 54 | *CMD (Windows)* .bat file example: 55 | 56 | ``` 57 | @echo off 58 | echo Starting conversion... 59 | for %%f in (*.[EXT]) do echo "%%f" & python ray2get.py [options] "%%f" 60 | echo Converted. 61 | @echo on 62 | pause 63 | ``` 64 | 65 | *Bash* .sh script example: 66 | 67 | ``` 68 | #!/bin/bash 69 | 70 | echo Starting conversion... 71 | for f in *.[EXT] 72 | do 73 | echo "${f}" 74 | python ray2get.py [options] "${f}" 75 | done 76 | echo Converted. 77 | ``` 78 | 79 | In either script, replace `[EXT]` with the input file extension (`wav` for encoding, `apm` for decoding), and `[options]` with whatever you need to do (`e` for encoding, `d` for decoding, etc., see previous section). Both `ray2get.py` and your shell script have to be in the same directory as the files you're trying to process. *(And don't forget to check cautionary note \#2 below!)* 80 | 81 | 82 | ### /!\\ Cautionary notes 83 | 84 | 1. The game engine doesn't support mono files as background music. If you're looking to replace the musics with your own, make sure your .wav files are stereo before converting. 85 | 86 | 2. Ray2Get won't ask for permission to overwrite any existing file. As an example, if you're trying to decode `foo.apm` without specifying an output file name, make sure you don't already have a file called `foo.wav` or else it will be overwritten! 87 | 88 | 89 | ## Special thanks to 90 | 91 | The folks from the RaymanPC forum; 92 | 93 | deton24 for pointing out that the game audio won't run at more than 22050 Hz, and (unknowingly?) helping me realize that it isn't accurate IMA ADPCM; 94 | 95 | imaginaryPineapple (from the [OpenRayman](https://github.com/imaginaryPineapple/OpenRayman) project) for the complementary info about the APM file format. 96 | 97 | 98 | **Resources on the ADPCM file format:** 99 | 100 | * Article on MultimediaWiki: https://wiki.multimedia.cx/index.php/IMA_ADPCM 101 | 102 | * Original IMA-ADPCM specification document (an OCR version in PDF is also available there): http://www.cs.columbia.edu/~hgs/audio/dvi/ 103 | -------------------------------------------------------------------------------- /apm_format_description.txt: -------------------------------------------------------------------------------- 1 | An unofficial APM format description 2 | (Rayman 2 music files) 3 | [last edit: 2018-09-04] 4 | 5 | 6 | Introductory note: this description was put together by Palorifgrodbierzrt 7 | (aka. Synthesis), with some help from imaginaryPineapple 8 | (https://github.com/imaginaryPineapple/OpenRayman). 9 | This is our current knowledge about the format, so keep in mind that there are 10 | still a few elements missing, or some things that are incorrect. 11 | 12 | 13 | A Rayman 2 .apm file is comprised of a WAVEFORMATEX chunk, followed by a 80-byte 14 | ADPCM header, containing the initial values for the ADPCM state machine, and 15 | finally the actual ADPCM data: 16 | 17 | 18 | -------|---------|--------|---------------------------------------------------- 19 | Offset | Size | Type | Information 20 | -------|---------|--------|---------------------------------------------------- 21 | 0x0 | 2 | short | Format tag (0x2000 for Ubisoft ADPCM) 22 | 0x2 | 2 | short | Channel count 23 | 0x4 | 4 | int | Sample rate 24 | 0x8 | 4 | int | Byte rate, as if it were uncompressed 16-bit PCM 25 | 0xC | 2 | short | Block alignment (usually = 1) 26 | 0xE | 2 | short | Bits per sample 27 | -------|---------|--------|---------------------------------------------------- 28 | 0x10 | 4 | int | Size of the next APM description chunk (including 29 | | | | this value); should be 0x50 (80 in decimal) 30 | 0x14 | 4 | char*4 | Signature/version number? (default value is "vs12") 31 | 0x18 | 4 | int | Total file size, in bytes 32 | 0x1C | 4 | int | Audio data length, in nibbles 33 | 0x20 | 4 | int | ?? (four consecutive 0xFF bytes) 34 | 0x24 | 4 | int | ?? (four consecutive null bytes) 35 | 0x28 | 4 | int | Parity flag (*real-time placeholder, see below) 36 | 0x2C | 12*chan | - | For each channel, last to first: 37 | | | | (eg. stereo files stores right channel first) 38 | | 4 | int | Initial PCM value (with 16-bit sign extension) 39 | | 4 | int | Initial ADPCM step index 40 | | 4 | byte*4 | Beginning of the ADPCM data (wtf?) (**see below) 41 | 0x38 | | | and so on... 42 | ... | ? | - | Null bytes for padding 43 | -------|---------|--------|---------------------------------------------------- 44 | 0x60 | 4 | char*4 | Data chunk header ("DATA") 45 | 0x64 | ? | char*? | ADPCM audio data 46 | -------|---------|--------|---------------------------------------------------- 47 | 48 | 49 | The ADPCM audio data is stored as follows: the channels are interleaved, and 50 | since each sample takes only 4 bytes, they are stored in pairs. For example, 51 | the beginning of a stereo file would look like 52 | 0xAB 0xCD 0xEF 0xGH ... 53 | A: left channel, 1st ADPCM sample 54 | B: left channel, 2nd sample 55 | C: right channel, 1st sample 56 | D: right channel, 2nd sample 57 | E: left channel, 3rd sample 58 | F: left channel, 4th sample 59 | G: right channel, 3rd sample 60 | H: right channel, 4th sample, and so on. 61 | 62 | If the total number of sample is odd, then for each channel, the last byte will 63 | be filled halfway. The least significant bits are left to 0. For example, the 64 | end of a stereo file would look like 65 | ... 0xY0 0xZ0 66 | with Y and Z being the last samples of respectively the left and right 67 | channels. 68 | 69 | 70 | The audio format itself should be IMA-ADPCM (see 71 | http://www.cs.columbia.edu/~hgs/audio/dvi/ and 72 | https://wiki.multimedia.cx/index.php/IMA_ADPCM for more information). However 73 | the game actually decodes it in a slightly different way: the least significant 74 | 3 bits of the "step" value are cleared at the beginning of each iteration. 75 | 76 | 77 | (*) At address 0x28, there's a 4-bit reserved value which should be equal to 0. 78 | This is because the header is copied in memory while the game runs, and some 79 | areas of the header are actually manipulated (but the file itself is untouched). 80 | This particular value is a placeholder for a parity flag: since ADPCM is 4-bit, 81 | there are two samples per byte; this value is used in the game's code to 82 | indicate whether it is currently processing the odd (most significant) or even 83 | (least significant) nibble. 84 | 85 | 86 | (**) For some reason, the beginning of the ADPCM chunk is stored in the header 87 | in an unusual way: the 4-byte value corresponds to the first four bytes of the 88 | (multi-channel interleaved) ADPCM data, starting from the byte corresponding to 89 | the channel. Say we have a stereo file. In the header appears the following 90 | sequence: 91 | -------|---------|--------|---------------------------------------------------- 92 | Offset | Size | Type | Information 93 | -------|---------|--------|---------------------------------------------------- 94 | | | | ... 95 | 0x2C | 4 | int | Initial PCM value for right channel 96 | 0x30 | 4 | int | Initial ADPCM step index for right channel 97 | 0x34 | 4 | byte*4 | Bytes 2 through 5 of the ADPCM data chunk <--- 98 | 0x38 | 4 | int | Initial PCM value for left channel 99 | 0x3C | 4 | int | Initial ADPCM step index for left channel 100 | 0x40 | 4 | byte*4 | First four bytes of ADPCM data <--- 101 | 0x44 | 28 | - | Null bytes for padding 102 | | | | ... 103 | -------|---------|--------|---------------------------------------------------- 104 | 105 | Note: there are 52 bytes available for storing the initial ADPCM variables, 106 | which means that the APM format could support up to 4 channels, leaving 4 null 107 | padding bytes. 108 | -------------------------------------------------------------------------------- /file_sequences.txt: -------------------------------------------------------------------------------- 1 | == Sequences of audio files played in Rayman 2 2 | 3 | Last updated 2018-05-12. 4 | 5 | Notes: 6 | Brackets are used to define loops: [list_of_files](number_of_loops) 7 | [...](*) = repeat infinitely 8 | xx01..xx04 = all the files from xx01 to xx04 (ie. xx01 xx02 xx03 xx04) 9 | [fileA|fileB|fileC] = random pick between fileA, fileB or fileC 10 | 11 | 12 | -- Title 13 | Main menu: [MM01..MM10](*) 14 | 15 | 16 | Intro sequence: Jail10 [silence](*) 17 | "Ly, the fairy?": JailMus [silence](*) 18 | Escaping the ship: [Na04..Na11](*) 19 | 20 | 21 | -- The Woods of Light 22 | "Globoooox!": Fir01 [[Fir02..Fir04](3) [FO24..31](2) [Fir02..Fir04](2) FO04..FO15](*) 23 | Murfy: FO02 FO03 [FO04..FO15 Fir01..Fir04 FO24..FO31 Fir01..Fir04](*) 24 | "Not daddy Globox?": [FO16..23](*) 25 | Narrow passage + climbing: [FO12..FO15 FO08..FO15 Fir01..Fir04 FO24..FO31](*) 26 | King of the Teensies: [PS01..PS04](*) 27 | 28 | 29 | -- The Fairy Glade 30 | Mushroom area: Ff01..Ff10 Ff03..Ff10 [Ffr01..Ffr08 Ffr01..Ffr12 Ffr09..Ffr12 [Ff03..Ff10](2)](*) 31 | Piranha: [Ff11..Ff22 Ff11..Ff18 [Ffr09..Ffr12](2) Ffr01..Ffr12](*) 32 | Door switch on the tree: [[Ff23..Ff34](2) [Ff19..Ff22](2) [Ffr09..Ffr12](2) Ffr01..12 Ffr10..12](*) 33 | Toxic waste: [[Ff35..Ff38](2) Ff39..Ff46 [Ff19..Ff22](2) [Ff11..Ff14](2) Ffr09..Ffr12 Ffr01..Ffr08](*) 34 | 35 | Climbing the tree: [[Ffr01..Ffr04](2) Ff43 Ff44 Ffr05..Ffr12 Ff05..Ff08](*) 36 | Slide + unreachable cage: [[Ff19..Ff22](2) Ff11..Ff18 Ff11..Ff14 [Ffr09..Ffr12](2)](*) 37 | 38 | Cutscene (first 2 seconds): [silence](*) 39 | Pirate: BA1R01..BA1R08 [BA1R15 [BA1R16](7) BA1R17..BA1R24](*) 40 | Quiet area on the left: [silence](*) 41 | Thrown barrels: [BA109..BA116 BA1R15 BA1R16 [BA109..BA112](2) BA113..BA116 BA1R15 BA1R16 BA1R01..BA1R08 BA1R17..BA1R26](*) 42 | "Use your shadow and your helicopter": [silence](*) 43 | Secret agent: [BA301..BA306 [BA3R01..BA3R09](2)](*) 44 | Strafe fight: BA1R01..BA1R24 [BA1R09..BA1R24](*) 45 | End of fight: [silence](*) 46 | Sleeping pirate next to the ladder: [Wa2r01..Wa2r16](*) 47 | Lasers: see (Secret agent) 48 | Sleeping pirate next to the switch: see (Sleeping pirate next to the ladder) 49 | End of fight: [silence](*) 50 | 51 | Lasers, pt. 2: see (Secret agent) 52 | 53 | Destroy the machine: [BA209..BA216](*) 54 | Ly: [CI01..CI04 CIF](*) 55 | Purple Lums: [silence](*) 56 | 57 | Pipes: Sw01 [Sw02](*) 58 | Air currents: [[Ffr01..Ffr04](2) Ff43 Ff44 Ffr05..Ffr12 Ff05..Ff08](*) 59 | Teensie cage broken: [PS01..PS04](*) 60 | Teensie dance: [silence](*) 61 | 62 | Bonus level access denied/granted: [PS01..PS04](*) 63 | 64 | 65 | -- The Marshes of Awakening 66 | Entering the marshes: [SKO00..SKO08](*) 67 | Surfing with Ssssam: [[Sk08..Sk10](2) [Sk11..Sk14](2)](*) 68 | Respawn after losing a life: [silence](*) 69 | 70 | Surfing with Ssssam, pt. 2: [Sk01..Sk07 [Sk15 Sk16](2)](*) 71 | Standing on the fragile platform: [silence](*) 72 | Farewell, Ssssam: [Th03..Th05](*) 73 | Teensie cage broken: [PS01..PS04](*) 74 | Teensie dance: [silence](*) 75 | 76 | Bonus level access denied/granted: [PS01..PS04](*) 77 | (This sequence is played at the end of every level, therefore it is last shown 78 | here; same for the bonus level, which is only shown once below.) 79 | 80 | 81 | -- Bonus level 82 | Cutscene: [silence](*) 83 | Race: [LY1R05..LY1R12 LY1R05..LY1R08 LY1R09_2 LY1R10_2 LY1R11_2 LY1R12_2](*) 84 | Race again: [Ly2r01..Ly2r16](*) 85 | End of race: [silence](*) 86 | 87 | 88 | -- The Bayou, pt. 1 89 | Pirate ship cutscene: [BA01..BA04](*) 90 | Entering the Bayou: SWCI Swc01 SWC02 [SWC03..SWC08](*) 91 | 92 | -- The Walk of Life 93 | 3, 2, 1, Go!: LY101..LY104 [[LY101 LY101_2](2) LY103 LY104](*) 94 | Bouncy spider webs: LY105..LY112 [[LY1R07 LY1R08](2) LY1R09..LY1R12 [LY1R07 LY1R08](2) LY1R09_2 LY1R10_2 LY1R11_2 LY1R12_2 [LY107 LY108](2) LY109..LY112](*) 95 | Narrow passage + spider web: see (Bonus level::Race) 96 | Narrow passage 2 + angled platforms: [LY113..LY116](*) 97 | Narrow passage 3 + angled platforms 2: see (Narrow passage + spider web) 98 | End of race: [silence](*) 99 | Teensie (for 4 seconds): [PS01..PS04](*) 100 | Teensie dance: [silence](*) 101 | 102 | -- The Bayou, pt. 2 103 | First cage, after moving platform: [SWC09..SWC12](*) 104 | Switch + falling platforms: [SWC14..SWC21](*) 105 | Sleeping pirate under the cage: [silence](*) 106 | Fight: [Wa2r01..Wa2r16](*) 107 | After the fight: [silence](*) 108 | Purple lums + zombie chickens: [Sw03..Sw10](*) 109 | Bombs on the platforms: SWC29 [SWC13..SWC25 SWC22..SWC29](*) 110 | 111 | Piranha: [[CH301..CH304](2) [CH2R01..CH2R04](2)](*) 112 | A pirate appears: [silence](*) 113 | Fight #2: BA1R01..BA1R24 [BA1R09..BA1R24](*) 114 | After the fight #2: [silence](*) 115 | Barrels around the giant tree: [CH2R01..CH2R04](*) 116 | Barrels rolling down the short platforms: CH2R05..CH2R08 CH2R17..CH2R20 [CH2R18 CH2R20](*) 117 | Timer and suspended axes: [[CH305..CH312](2) CH2R01..CH2R20](*) 118 | The last pipe: [silence](*) 119 | Teensie cage broken: [PS01..PS04](*) 120 | Teensie dance: [silence](*) 121 | 122 | 123 | -- The Sanctuary of Water and Ice 124 | Teensie council room: [PS01..PS04](*) 125 | Fighting the pirate: [Wa108..Wa109](*) 126 | On the bay: WA101..WA103 WA101_2 WA102_2 WA103_2 WA104_2 WA105_2 WA106 WA107 WA1Rns01..WA1Rns04 WA1R04 WA1R05 [WA101..WA103 WA101_2 WA102_2 WA103_2 WA104_2 WA105_2 WA106 WA107 WA1Rns01..WA1Rns08 WA101_2 WA102_2 WA103_2 WA1R01..WA1R03 WA101 WA101_2 WA1R01 WA101 WA1R01 WA101_2 WA1Rns01 WA101 WA101_2 WA101 [WA101_2](6) WA104_2 WA105_2 WA106 WA107 WA1R04 WA1R05 WA104_2 WA105_2 WA106 WA107 WA1Rns01..WA1Rns04 WA1R04 WA1R05](*) 127 | The two orbs: WA110..WA117 WA110..WA116 WA117_2 [WA110..WA113 WA1R12..WA1R15 WA110..WA115 WA1R08..WA1R15](*) 128 | 129 | Murfy: [AS01..AS06](*) 130 | The attack run: [AS01..AS10](*) 131 | Aglagl: [He305..He314 HE301_2 He302..He304](*) 132 | Boss defeated: JINGL7 [silence](*) 133 | The sanctuary: [WA301 WA301_2 WA302..WA312 [[WA306|WA308|WA312] WA307](2) WA309..WA312 [[WA306|WA308|WA312] WA307](3) WA304..WA306 WA303..WA306 [[WA306|WA308|WA312] WA307](2) [WA309..WA312](2) [[WA306|WA308|WA312] WA307](3)](*) 134 | The mask: Mask [silence](*) 135 | 136 | Polokus: [PO01..PO04](*) 137 | 138 | 139 | -- The Menhir Hills pt. 1 140 | Murfy: [silence](*) 141 | First shell ride: [ROR05..ROR12](*) 142 | Two pirates sleeping: [Wa2r01..Wa2r16](*) 143 | Between/after the fights: [silence](*) 144 | 145 | Mushroom in the tree: [silence](*) 146 | Shell ride pt. 2 between the rising tentacles: [RO105..RO108 ROR05..ROR08](*) 147 | Barrel ride under the slope: [CA201 CA202](*) 148 | Sleeping pirate in front of the door: [Na401..Na416](*) 149 | Unlocking the door: [silence](*) 150 | Clark pt. 1: RO201..RO204 [RO207..RO212 RO207..RO210 RO203 RO204 RO201 RO202 RO207..RO212](*) 151 | 152 | -- The Cave of Bad Dreams 153 | The Cave pt. 1: VU1R01..VU1R06 [VU1R01_2 VU1R02..VU1R06](*) 154 | The Cave pt. 2: [VU101..VU112](*) 155 | Fighting against mini Janos: see (The Cave pt. 1) 156 | Room with the locked door: [VU109..VU112 [VU1R01_2 VU1R02..VU1R06](2) VU109..VU112 VU101..VU112](*) 157 | Looking for the orbs: see (The Cave pt. 1) 158 | 159 | The calm before the storm: see (The Cave pt. 1) 160 | Slide + fight against Jano: VU2I [VU202 VU203](*) 161 | Golden coins!: see (Room with the locked door) 162 | "I want the treasure!": [silence](*) 163 | "No treasure for me!" (after the END): see (The Cave pt. 1) 164 | Back to the marshes: see (The Marshes of Awakening::Entering the marshes) 165 | 166 | -- The Menhir Hills pt. 2 167 | Clark pt. 1: see (The Menhir Hills pt. 1::Clark pt. 1) 168 | Clark pt. 2, wueaaagh!: RO205..RO216 [RO201..RO216](*) 169 | The last cages: [silence](*) 170 | Last shell ride: [[RO109..RO111 RO110 ROR09..ROR12](2) RO105..RO108 ROR05..ROR12](*) 171 | 172 | 173 | -- The Canopy 174 | Portal: [silence](*) 175 | Spider!: MO101 [MO102..MO121](*) 176 | Spider web: [[GLRL02](2) GLRL01..GLRL03 GLRL02 GLRL01 [GLRL02](2) GLRL01..GLRL03 GLRL02 GLRL01 [GLRL02](4)](*) 177 | 178 | Narrow platforms: see (Spider web) 179 | Pirate!: BA1R01..BA1R08 [BA1R09..BA1R24](*) 180 | After the fight: [silence](*) 181 | "Public enemy no. 2": [GL01..GL06](2) GLR01..GLR08 GLR01 GLR02 GL03 GLR04 GL05 GLR06..GLR08 goto(Spider web) 182 | Rain dance: [GL09..GL20](2) GL09..GL12 [GL09 GL10](2) goto("Public enemy no. 2") 183 | Another pirate!: [Wa2r01..Wa2r16](*) 184 | "Globox not afraid" + silver lum: [silence](*) 185 | 186 | Pirate ship appears and drops bombs: [GL37..GL40 GL21 GL22](*) 187 | Pirate with rocket launcher: [Wh301 Wh302](*) 188 | After the fight: [silence](*) 189 | "Just a big nosed bush": [GL23..GL42](*) 190 | Teensie cage broken: [PS01..PS04](*) 191 | Teensie dance: [silence](*) 192 | 193 | 194 | -- The Whale Bay 195 | Initial area: [silence](*) 196 | Electric barrel: [Na401..Na416](*) 197 | Aquarium: Wh201..Wh204 [Wh205..Wh210 Wh209 Wh210](*) 198 | Pirate on the ship: [Wa201..Wa214](*) 199 | After the fight: [silence](*) 200 | Pirate sleeping against palm trees: [Wh301 Wh302](*) 201 | 202 | "Carmen the whale is trapped": [silence](*) 203 | Underwater: see (Aquarium) 204 | Switch room + walking shell: [silence](*) 205 | Shell ride: [ROR05..ROR12](*) 206 | "To breathe, grab my air bubbles": WH101..WH116 [WH117..WH156](*) 207 | 208 | Aerophagic piranhas: see ("To breathe, grab my air bubbles") 209 | Stranded ship: [silence](*) 210 | Teensie cage broken: [PS01..PS04](*) 211 | Teensie dance: [silence](*) 212 | 213 | 214 | -- The Sanctuary of Stone and Fire 215 | Teensie council room: [PS01..PS04](*) 216 | Big leap: [Pl101..Pl128](*) 217 | Pirate!: [BA209..BA216 [BA210 BA212](2) BA209..BA212 BA209..BA216](*) 218 | After the fight: [BA201..BA208 [BA204 BA201_2 BA202..BA204 BA204..BA208](2)](*) 219 | Purple lums and golden fist: [PL201..PL208](*) 220 | Pirate #2: [WH301 WH302](*) 221 | After the fight #2: [silence](*) 222 | Plum rides towards the cages: [PL209..PL212](4) [PL201..PL212](*) 223 | Flies under the spider webs: see (Purple lums and golden fist) 224 | 225 | Entering the temple: [PL401..PL406 PL4R01..PL4R06 PL401..PL403 PL4R04 PL4R05 PL405 PL406 [PL4R01..PL4R06](2)](*) 226 | Icicles: [PL4R01..PL4R06](*) 227 | Big moving platform on the lava: [[PL407 PL408](4) PL4R01..PL4R04](*) 228 | Flamethrowers and icicles: see (Icicles) 229 | Three doors: see (Plum rides towards the cages) 230 | Sleeping shell: [silence](*) 231 | Shell ride: ROR09..ROR12 [ROR05..ROR12](*) 232 | 233 | Outside the temple: see (Big leap) 234 | Down the lava river + pirate #3: see (Purple lums and golden fist) 235 | Sneaky mini Jano: [VU109..VU112 [VU1R01_2 VU1R02..VU1R06](2) VU109..VU112 VU101..VU112](*) 236 | Blue skies of freedom: [silence](*) 237 | 238 | Inside the sanctuary: [PL301..PL312](*) 239 | The yellow orb: see (Big moving platform on the lava) 240 | A new path unlocks: [PL501..PL504](*) 241 | 242 | Platforms over hot lava: see (Big moving platform on the lava) 243 | Sliding towards the purple lums: [[Ca1r07_2](2) Ca1r07 Ca1r08](*) 244 | The blue orb: PL505..PL514 [PL501..PL514](*) 245 | The mask appears: see (The Sanctuary of Water and Ice::The sanctuary) 246 | Acquiring the mask: Mask [silence](*) 247 | 248 | Polokus: [PO01..PO04](*) 249 | 250 | 251 | -- The Echoing Caves pt. 1 252 | Intro cutscene + pirate!: see (The Sanctuary of Stone and Fire::Pirate!) 253 | After the fight: see (The Sanctuary of Stone and Fire::After the fight) 254 | Underwater tunnel: [BA3R09 [BA3R08](3) BA3R05 BA3R06 BA3R08 BA3R01 BA3R02 BA3R09 BA3R08 BA3R07 BA3R08 BA306](*) 255 | Sleeping pirate next to the switch: [silence](*) 256 | Pirate fight #2: [WA201..WA214](*) 257 | After the fight #2: [silence](*) 258 | Last switches: see (The Sanctuary of Stone and Fire::After the fight) 259 | 260 | Inside the caves: [silence](*) 261 | First barrel ride: [CA101..CA108](*) 262 | Second barrel ride: CA105..CA108 goto(First barrel ride) 263 | End of area: [CA109..CA112](4) [CA113 CA114 [Ca1r07_2](2)](2) [CA109..CA112](2) CA113 CA114 CA114F [silence](*) 264 | 265 | -- The Fairy Glade pt. 2 266 | Back in the Fairy Glade: see (The Fairy Glade::Climbing the tree) 267 | Pillars in the water: [silence](*) 268 | Watch out for the barrels!: see (The Fairy Glade::Thrown barrels) 269 | Sleeping pirate next to the cage: [BA101..BA108 BA1R09..BA1R14 BA1R01..BA1R14](*) 270 | After the fight: [silence](*) 271 | 272 | -- The Echoing Caves pt. 2 273 | Deadly liquid: [silence](*) 274 | Barrel ride #1: CA201..CA204 [CA205 CA206 [[CA201 CA202](2) [CA203](2)](2)](*) 275 | Pirate next to the closed gate: see (The Echoing Caves pt. 1::End of area) 276 | Barrel ride #2: CA204..CA206 [CA201..CA206](*) 277 | Barrel ride #3: [CA201 CA202](*) 278 | Teensie cage broken: [PS01..PS04](*) 279 | Teensie dance: [silence](*) 280 | 281 | 282 | -- The Precipice 283 | Meanwhile in the prison ship: [BA01..BA04](*) 284 | Entering the Precipice: [Na28..Na31](*) 285 | First checkpoint: Na04..Na27 [Na28..Na31](*) 286 | End of pt. 1: Na32..Na51 [Na04..Na11](*) 287 | 288 | Straight line: [Na04..Na11](*) 289 | Round the collapsing tower: [Na309..Na311 NA312.2](*) 290 | Pirate!: [Na401..Na416](*) 291 | After the fight: [silence](*) 292 | 293 | Entering area 3: see (Entering the Precipice) 294 | Area 3: Na32..Na51 [Na28..Na51](*) 295 | The great descent: [Na301..Na308](*) 296 | Fragile platforms: see (Round the collapsing tower) 297 | A safe place: [silence](*) 298 | Pirate! #2: [WH301..WH306](*) 299 | After the fight #2: [silence](*) 300 | Teensie cage broken: [PS01..PS04](*) 301 | Teensie dance: [silence](*) 302 | 303 | 304 | -- The Top of the World 305 | Intro cutscene: [SE153..SE168](*) 306 | Getting rid of the gorilla pirate: [silence](*) 307 | Chair ride pt. 1: [SE121..SE128](*) 308 | Chair ride pt. 2: [SE129..SE136](*) 309 | Chair ride pt. 3, green lum: SE137..SE144 [SE121..SE144](*) 310 | Respawning: [SE101..SE116](*) 311 | 312 | Fighting the barrel: [[SE2329..SE2332](2) SE2305..SE2320](*) 313 | Rolling barrels in the narrow passage: [SE2305..SE2320](*) 314 | End of level: [[SE153..SE168](2) [SE145..SE152](2)](*) 315 | Teensie cage broken: [PS01..PS04](*) 316 | 317 | 318 | -- The Walk of Power 319 | 3, 2, 1, Go!: [Ly2r01 Ly2r02](*) 320 | The Walk of Power: [[LY201..LY206](2) [Ly2r01..Ly2r06](2)](*) 321 | Water lilies pt. 1: LY207..LY217 [LY201..LY217](*) 322 | Water lilies pt. 2: [Ly2r01..Ly2r06](*) 323 | End of race: [silence](*) 324 | Teensie: [PS01..PS04](*) 325 | Teensie dance: [silence](*) 326 | 327 | 328 | -- The Sanctuary of Rock and Lava 329 | The Swamp: Sw01 [Sw02](*) 330 | Pirate with a hook: [Wh301..Wh306](*) 331 | After the fight: [silence](*) 332 | Barrels: [Sw03..Sw10](*) 333 | Sanctuary entrance: [silence](*) 334 | Pirate #2: [Wa201..Wa214](*) 335 | After the fight #2: [silence](*) 336 | 337 | Entering the sanctuary: EA101..EA103 [EA104..EA122](*) 338 | Moving platforms: [EA201..EA208](*) 339 | Pillars in the lava: [EA209..EA212](*) 340 | 341 | The lava tower: [EA301..EA304](*) 342 | The last lums: [EA309..EA320 [EA301..EA304](2)](*) 343 | "Yeah!": see (The lava tower) 344 | End portal room: [silence](*) 345 | Teensie cage broken: [PS01..PS04](*) 346 | Teensie dance: [silence](*) 347 | 348 | 349 | -- Beneath the Sanctuary of Rock and Lava 350 | Teensie council room: [PS01..PS04](*) 351 | Hmmm...: [silence](*) 352 | A new power: [CI01..CI04 CIF](*) 353 | Beneath the sanctuary: [[HE01..HE04](2) [HE05](2)](*) 354 | Lava cascades: HE06 [HE11..HE13](*) 355 | Angular stone: see (Beneath the sanctuary) 356 | 357 | Cascades over a magma river: HE06 [HE07..HE10](*) 358 | Magma river pt. 2: see (Beneath the sanctuary) 359 | Last two cages: see (Lava cascades) 360 | 361 | "I can't fly anymore!": He305 He306 [He3r05 He3r06](*) 362 | Foutch: He305 He314 [HE301_2 He302..He314](*) 363 | Boss defeated: JINGL7 [silence](*) 364 | Stairway to the mask: see (The Sanctuary of Water and Ice::The sanctuary) 365 | Acquiring the mask: Mask [silence](*) 366 | 367 | Polokus: [PO01..PO04](*) 368 | 369 | 370 | -- The Tomb of the Ancients 371 | Meanwhile in the prison ship: [BA01..BA04](*) 372 | Big staircase: MO001 [MO002..MO005](*) 373 | Spider attack!: MO101 [MO102..MO121](*) 374 | Intersection: [silence](*) 375 | Pirate in the left room: [Wa201..Wa214](*) 376 | Pirate in the tomb: [Wh301..Wh302](*) 377 | Raised platforms: see (Big staircase) 378 | The 1000th lum: [BA209..BA216](*) 379 | 380 | Platforms on the deadly water: see (Big staircase) 381 | Pirate in front of the force field: see (Pirate in the left room) 382 | Laser!: see (Big staircase) 383 | Barrel room: [silence](*) 384 | Barrel ride: [CA201 CA202](*) 385 | Pirate in front of the exit: [Wa2r01..Wa2r16](*) 386 | Barrel room 2: see (Big staircase) 387 | Barrel ride 2: see (The Echoing Caves pt. 2::Barrel ride #1) 388 | The cage under the wooden platform: [silence](*) 389 | Pirate #5: see (Pirate in the left room) 390 | 391 | Zombie chickens everywhere!: see (Big staircase) 392 | Spider #2: [MO102..MO121](*) 393 | Yellow lums and force fields: see (Big staircase) 394 | Pirate throwing barrels: see (The Fairy Glade::Thrown barrels) 395 | Calm before the storm: [silence](*) 396 | Pirate #6: [Wh301..Wh306](*) 397 | 398 | Fighting remote-controlled Clark: [BA1R15 BA1R16](4) goto(The Fairy Glade::Strafe fight) 399 | Remote-controlled box destroyed: JINGL7 [silence](*) 400 | Teensie cage broken: [PS01..PS04](*) 401 | Teensie dance: [silence](*) 402 | 403 | 404 | -- The Iron Mountains 405 | Teensie council room: [PS01..PS04](*) 406 | In the rain: see (The Sanctuary of Stone and Fire::After the fight) 407 | Rotating room: [BA209..BA216](*) 408 | Grassland: [silence](*) 409 | Pirates #1, #2: see (The Fairy Glade::Thrown barrels) 410 | Golden fist: [silence](*) 411 | Pirate #3: [Wh301..Wh306](*) 412 | Pirate #4: [Wa2r01..Wa2r16](*) 413 | Pirate #5 next to the ladder: see (Pirate #3) 414 | Helicopter down the river: [Na309..Na312](2) [Na301..Na308](*) 415 | 416 | In the balloon: MI01..MI05 [Mif_1 [Mif_2](5)](*) 417 | Giant chicken: see (The Fairy Glade::Climbing the tree) 418 | Sliding into the building: [silence](*) 419 | Inside the building: see (The Top of the World::End of level) 420 | Sliding towards the sleeping shell: [silence](*) 421 | Shell ride: see (The Menhir Hills pt. 2::Last shell ride) 422 | 423 | Last lums + Uglette: [Mif_1 [Mif_2](5)](*) 424 | Rescued Globox babies: see (In the balloon) 425 | Mask obtained: Mask [silence](*) 426 | 427 | Polokus: [PO01..PO04](*) 428 | 429 | 430 | -- The Prison Ship 431 | Introducing the Grolgoth: [BA01..BA04](*) 432 | The slide: [BO01 BO02 [BO03 BO04](3)](*) 433 | Switches: [BO05..BO09 Bof10 BO05 BO06 [BO07 BO08](5) BO09 Bof10](*) 434 | 435 | Shell generator disabled: [silence](*) 436 | Pirate with flamethrower: [He301..He314](*) 437 | The flying shell: [AS01..AS06](*) 438 | 439 | The flying shell pt. 2: AS07..AS10 [AS01..AS10](*) 440 | 441 | 442 | -- The Crow's Nest 443 | "Let's see how well you swim in molten lava": [BA01..BA04](*) 444 | Fighting the Grolgoth: [RH01..RH12 [RH07 RH08](2) RH09..RH12 [RH01..RH04 [RH09|RH12]](3) RH01..RH06 [RH07 RH08](2) RH09..RH12](*) 445 | Ly sends help: [CI01..CI04 CIF](*) 446 | The final fight: AS07 [RH01..RH12 RH01..RH04 He307..He314 He301..He314 RH05..RH08](*) 447 | One last surprise: JINGL7 silence [Th06 Th07](*) 448 | 449 | The mourning: EN101..EN105 EN1F [silence](*) 450 | Rayman appears: EN200..EN204 EN2F [silence](*) 451 | 452 | End credits: Th01..Th07 Na12..Na51 [Na04..Na51](*) 453 | 454 | 455 | -- Miscellaneous 456 | Game over: JINGLE8 [silence](*) 457 | Try again: new01 [silence](*) 458 | -------------------------------------------------------------------------------- /ray2get.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Ray2Get 2.0 5 | by Palorifgrodbierzrt 2018 6 | [ver. 2018-09-04] 7 | 8 | 9 | Ten years later (or maybe ten years too late?), I'm back! 10 | Here is an updated revision of my music-ripping tool for the PC version of 11 | Rayman 2 (now written in Python for convenience). 12 | 13 | This version fixes a minor inaccuracy in the decoding of APM files, and is much 14 | more versatile. You can now convert from .apm to .wav... and back! 15 | 16 | More formats are supported: mono and stereo files (too bad mono files can't be 17 | played in the game, though...), any sample rate (but the game will resample to 18 | 22050Hz anyway), and it can read 8, 16, 24 and 32-bit integer PCM wav files 19 | (floating point is not supported). 20 | 21 | The game uses a slight variation of the standard IMA-ADPCM algorithm. Every 22 | time a nibble is processed, the ADPCM step variable has its least significant 3 23 | bits cleared, for some reason. 24 | While decoding, the game ditches the first PCM sample (located in the APM 25 | header), so I included two decoding modes: one which processes .apm files like 26 | the game does, and another one which keeps the first sample in the output file 27 | (in case you're a purist like me :p). 28 | 29 | 30 | Usage: 31 | Encoding, for proper play in game: python ray2get.py e file.wav [file.apm] 32 | Encoding, standard IMA-ADPCM: python ray2get.py ei file.wav [file.apm] 33 | 34 | Decoding, as the game would: python ray2get.py d file.apm [file.wav] 35 | Decoding, standard IMA-ADPCM: python ray2get.py di file.apm [file.wav] 36 | Decoding, keep 1st sample: python ray2get.py d1 file.apm [file.wav] 37 | 38 | Flags 'i' and '1' for APM decoding are optional and can be both used 39 | interchangeably at the same time. 40 | The last parameter (output file name) is optional. If not provided, the output 41 | file name will be the same as the input file name, with the extension replaced 42 | with .apm (or appended if the input file has no extension). 43 | 44 | 45 | Special thanks to the folks from the Rayman Pirate Community, deton24 for 46 | (unknowingly) pointing some of my bugs out, imaginaryPineapple (from the 47 | OpenRayman project) for the complementary info about the APM file format. 48 | """ 49 | 50 | 51 | from __future__ import print_function 52 | import os.path, re, struct, sys 53 | 54 | 55 | # ----------------------------------------------------------------------------- 56 | # Common data 57 | # ----------------------------------------------------------------------------- 58 | 59 | def toList(value, bytes=4, little_endian=True): 60 | """ 61 | Converts an integer to a list of bytes (useful for file binary output). 62 | 63 | value: an integer value 64 | bytes: the number of bytes in the output list 65 | little_endian: if True, the list will contain the least significant 66 | byte first 67 | """ 68 | v = value 69 | l = [] 70 | for b in range(bytes): 71 | l.append((v & 255)) 72 | v = v >> 8 73 | if little_endian == False: 74 | l = l[::-1] # flip the list backwards 75 | return l 76 | 77 | 78 | # The IMA index table 79 | index_table = [-1, -1, -1, -1, 2, 4, 6, 8] 80 | 81 | # The IMA step table 82 | step_table = [ 83 | 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 84 | 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 85 | 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 86 | 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 87 | 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 88 | 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 89 | 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 90 | 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 91 | 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 92 | ] 93 | 94 | 95 | 96 | # ----------------------------------------------------------------------------- 97 | # Encoder section 98 | # ----------------------------------------------------------------------------- 99 | 100 | def encode_init(sample1, sample2): 101 | """ 102 | Initialize the ADPCM encoding, using the first two PCM samples of the 103 | audio signal. 104 | 105 | See http://www.cs.columbia.edu/~hgs/audio/dvi/ for more information. 106 | """ 107 | predictor = sample1 108 | diff = sample2 - sample1 109 | 110 | if diff < 0: 111 | diff = -diff 112 | elif diff > 32767: 113 | diff = 32767 114 | stepindex = 0 115 | while step_table[stepindex] < diff: 116 | stepindex += 1 117 | 118 | return predictor, stepindex 119 | 120 | 121 | def encode(sample, predictor, stepindex, ima_adpcm=False): 122 | """ 123 | Perform one step of the ADPCM encoding algorithm, with given inputs. 124 | 125 | sample: the next PCM sample to encode 126 | predictor: the current PCM predictor value 127 | stepindex: the current ADPCM step index 128 | ima_adpcm: if False, the lowest 3 bits of the step variable will be 129 | cleared 130 | 131 | See http://www.cs.columbia.edu/~hgs/audio/dvi/ for more information. 132 | """ 133 | delta = sample - predictor 134 | nibble = 0 135 | if delta < 0: 136 | nibble = 8 137 | delta = -delta 138 | 139 | step = step_table[stepindex] 140 | diff = step >> 3 141 | if not ima_adpcm: 142 | step &= (~7) 143 | 144 | if delta >= step: 145 | nibble |= 4 146 | delta -= step 147 | diff += step 148 | step >>= 1 149 | if delta >= step: 150 | nibble |= 2 151 | delta -= step 152 | diff += step 153 | step >>= 1 154 | if delta >= step: 155 | nibble |= 1 156 | diff += step 157 | 158 | if nibble & 8: 159 | predictor -= diff 160 | else: 161 | predictor += diff 162 | if predictor < -32768: 163 | predictor = -32768 164 | elif predictor > 32767: 165 | predictor = 32767 166 | 167 | stepindex += index_table[nibble & 7] 168 | if stepindex < 0: 169 | stepindex = 0 170 | elif stepindex > 88: 171 | stepindex = 88 172 | 173 | return nibble, predictor, stepindex 174 | 175 | 176 | def read_sample_16(file_handler, bytes_per_sample): 177 | """ 178 | Reads a PCM sample from a file, and converts it to 16 bits. 179 | 180 | file_handler: a file handler to .read() the bytes from 181 | bytes_per_sample: an integer between 1 and 4 (only 8, 16, 24 and 32-bit 182 | files are supported) 183 | """ 184 | data = file_handler.read(bytes_per_sample) 185 | if data == '': 186 | return 0 187 | 188 | if bytes_per_sample == 1: 189 | return (struct.unpack('> 8) 194 | elif bytes_per_sample == 4: 195 | return (struct.unpack('> 16) 196 | 197 | return 0 198 | 199 | 200 | def encode_file(wav_filename, apm_filename, ima_adpcm=False): 201 | """ 202 | Converts a PCM .wav file to the Rayman 2 .apm format. 203 | 204 | wav_filename: the input file name 205 | apm_filename: the output file name 206 | ima_adpcm: if True, standard IMA-ADPCM encoding is applied; 207 | if False, do the custom variant with modified step variable 208 | """ 209 | 210 | # Open WAV file 211 | pcm = open(wav_filename, 'rb') 212 | 213 | # Check format 214 | if pcm.read(4) != b'RIFF': 215 | print('Error: input file is not a RIFF file.', file=sys.stderr) 216 | pcm.close() 217 | return 2 218 | pcm.seek(4, 1) 219 | if pcm.read(4) != b'WAVE': 220 | print('Error: input file is not a WAVE file.', file=sys.stderr) 221 | pcm.close() 222 | return 2 223 | 224 | # Locate format subchunk 225 | chunkHeader = pcm.read(4) 226 | while chunkHeader and chunkHeader != b'fmt ': 227 | chunkSize = struct.unpack(' 2: 244 | print('Error: this file has ' + str(channel_count) \ 245 | + ' channels (only mono and stereo are supported).', 246 | file=sys.stderr) 247 | pcm.close() 248 | return 4 249 | pcm.seek(6, 1) 250 | bits_per_sample = struct.unpack('> 3 257 | 258 | # Locate data subchunk 259 | pcm.seek(12, 0) 260 | chunkHeader = pcm.read(4) 261 | while chunkHeader and chunkHeader != b'data': 262 | chunkSize = struct.unpack(' 0: 296 | for n in range(2): # Two nibbles per byte 297 | if not (remaining_samples == 0 and n == 1): 298 | for c in range(channel_count): 299 | sample[c] = read_sample_16(pcm, bytes_per_sample) 300 | nibble[c * 2 + n], predictor[c], step_index[c] = \ 301 | encode(sample[c], predictor[c], step_index[c], \ 302 | ima_adpcm) 303 | else: 304 | # If the data length is odd, the last nibble is set to null 305 | for c in range(channel_count): 306 | nibble[c * 2 + n] = 0 307 | remaining_samples -= 1 308 | 309 | for c in range(channel_count): 310 | adpcm_data.append(((nibble[c*2] & 15) << 4) + (nibble[c*2+1] & 15)) 311 | 312 | # End of encoding, close file 313 | pcm.close() 314 | 315 | # Add padding bytes if adpcm_data is less than 4 bytes long, otherwise the 316 | # header would be screwed up 317 | minimum_data_length = len(adpcm_data[channel_count*3-1:channel_count*3+3]) 318 | too_short_padding_length = 0 319 | if minimum_data_length < 4: 320 | adpcm_data.extend([0 for i in range(minimum_data_length, 4)]) 321 | too_short_padding_length = (4 - minimum_data_length) 322 | 323 | # Create and open APM file 324 | adp = open(apm_filename, 'wb') 325 | 326 | # APM file rendering... 327 | # Header 328 | adp.write(bytearray([0, 0x20])) # Format tag (2) 329 | adp.write(bytearray(toList(channel_count, 2))) # Channel count (2) 330 | adp.write(bytearray(toList(sample_rate))) # Sample rate (4) 331 | adp.write(bytearray(toList(sample_rate*channel_count*2))) # Byte rate (4) 332 | # (note: byte rate is stored as if the file was uncompressed PCM16) 333 | adp.write(bytearray([1, 0, 4, 0, 0x50, 0, 0, 0])) 334 | # Block align (2), Bit rate (2), APM chunk size (4) 335 | adp.write(bytearray(b'vs12')) # Ubisoft ADPCM version identifier (4) 336 | file_length = ((data_length+1 if data_length & 1 else data_length) >> 1) \ 337 | * channel_count + 100 + too_short_padding_length 338 | adp.write(bytearray(toList(file_length))) # Total file length in bytes (4) 339 | adp.write(bytearray(toList(data_length))) # Sample count (4) 340 | adp.write(bytearray([0xff, 0xff, 0xff, 0xff])) # Unknown (4) 341 | adp.write(bytearray(toList(0, 8))) # Unknown (4), Parity flag (4) 342 | for c in reversed(range(channel_count)): 343 | adp.write(bytearray(toList(adpcm_data[c*2]))) # Initial PCM value (4) 344 | adp.write(bytearray(toList(adpcm_data[c*2+1]))) # Initial step index (4) 345 | adp.write(bytearray(adpcm_data[channel_count*2+c:channel_count*2+c+4])) 346 | # Audio data start (4) 347 | adp.write(bytearray([0 for i in range(52 - channel_count*12)])) # Padding 348 | 349 | # Data section 350 | adp.write(bytearray(b'DATA')) # Data chunk header 351 | adp.write(bytearray(adpcm_data[channel_count*2:])) # ADPCM data (skipping 352 | # the initial values 353 | 354 | # End of rendering, close file 355 | adp.close() 356 | 357 | return 0 358 | 359 | 360 | 361 | # ----------------------------------------------------------------------------- 362 | # Decoder section 363 | # ----------------------------------------------------------------------------- 364 | 365 | def decode(nibble, stepindex, step, predictor, ima_adpcm=False): 366 | """ 367 | Perform one step of the ADPCM decoding algorithm, with given inputs. 368 | 369 | nibble: one ADPCM nibble 370 | stepindex: the current ADPCM step index 371 | step: the current ADPCM step 372 | predictor: the last decoded PCM variable 373 | ima_adpcm: if False, the lowest 3 bits of the step are cleared 374 | 375 | See http://www.cs.columbia.edu/~hgs/audio/dvi/ and 376 | https://wiki.multimedia.cx/index.php/IMA_ADPCM for more information. 377 | """ 378 | stepindex += index_table[(nibble & 7)] 379 | diff = step >> 3 380 | if not ima_adpcm: 381 | step &= (~7) 382 | 383 | if nibble & 1: 384 | diff += (step >> 2) 385 | if nibble & 2: 386 | diff += (step >> 1) 387 | if nibble & 4: 388 | diff += step 389 | if nibble & 8: 390 | diff = -diff 391 | predictor += diff 392 | if predictor > 32767: 393 | predictor = 32767 394 | elif predictor < -32768: 395 | predictor = -32768 396 | if stepindex > 88: 397 | stepindex = 88 398 | elif stepindex < 0: 399 | stepindex = 0 400 | step = step_table[stepindex] 401 | 402 | return predictor, stepindex, step 403 | 404 | 405 | def decode_file(apm_filename, wav_filename, ima_adpcm=False, \ 406 | preserve_first=False): 407 | """ 408 | Converts a Rayman 2 .apm file to 16-bit PCM .wav. 409 | 410 | apm_filename: the input file name 411 | wav_filename: the output file name 412 | ima_adpcm: if True, standard IMA-ADPCM decoding is applied; 413 | if False, do the custom variant with modified step variable 414 | """ 415 | # File buffers 416 | adp = open(apm_filename, 'rb') 417 | 418 | # Check format 419 | adp.seek(0x14, 0) # (0 == SEEK_SET) 420 | if adp.read(4) != b'vs12': 421 | print('Error: invalid file format (couldn\'t find .apm identifier).', 422 | file=sys.stderr) 423 | adp.close() 424 | return 2 425 | adp.seek(0, 0) 426 | 427 | # Read audio information 428 | format_tag, channel_count, sample_rate = \ 429 | struct.unpack(' 2: 436 | print('Error: this file has ' + str(channel_count) \ 437 | + ' channels (only mono and stereo are supported).', 438 | file=sys.stderr) 439 | adp.close() 440 | return 4 441 | adp.seek(0x1C, 0) 442 | data_length = struct.unpack(' 0: 490 | for c in range(channel_count): 491 | # Read one byte of data per channel 492 | nibble[c] = struct.unpack('> (n << 2), step_index[c], \ 503 | step[c], predictor[c], ima_adpcm) 504 | # Writing output 505 | pcm.write(bytearray(toList(predictor[c], 2))) 506 | 507 | remaining_samples -= 2 508 | adp_data = adp.read(channel_count) 509 | 510 | # End of decoding, close the files 511 | pcm.close() 512 | adp.close() 513 | 514 | return 0 515 | 516 | 517 | 518 | # ----------------------------------------------------------------------------- 519 | # Main function 520 | # ----------------------------------------------------------------------------- 521 | 522 | def main(): 523 | # Check the command syntax 524 | err_arg = False 525 | if len(sys.argv) < 3: 526 | print('Not enough arguments provided.', file=sys.stderr) 527 | err_arg = True 528 | elif sys.argv[1].lower()[0] not in ['e', 'd']: 529 | print('Unknown action parameter (should be e[i] or d[i1]).', 530 | file=sys.stderr) 531 | err_arg = True 532 | if err_arg: 533 | print('Usage:') 534 | print('\tEncoding (finetuned for the game):') 535 | print('\t\tpython ' + sys.argv[0] + ' e file.wav [file.apm]') 536 | print('\tEncoding (using standard IMA-ADPCM algorithm):') 537 | print('\t\tpython ' + sys.argv[0] + ' ei file.wav [file.apm]') 538 | print('\tDecoding (as done in the game):') 539 | print('\t\tpython ' + sys.argv[0] + ' d file.apm [file.wav]') 540 | print('\tDecoding (using standard IMA-ADPCM algorithm):') 541 | print('\t\tpython ' + sys.argv[0] + ' di file.apm [file.wav]') 542 | print('\tDecoding (keep first PCM sample in output file):') 543 | print('\t\tpython ' + sys.argv[0] + ' d1 file.apm [file.wav]') 544 | return 1 545 | 546 | if not os.path.isfile(sys.argv[2]): 547 | print('Error: input file does not exist.', file=sys.stderr) 548 | return 1 549 | 550 | # Generate output file name if not provided 551 | if len(sys.argv) >= 4: 552 | output_filename = sys.argv[3] 553 | else: 554 | # Give the input file's name to the output file 555 | output_filename = re.sub('\.[^.]*$', '', sys.argv[2]) 556 | output_filename += '.apm' if sys.argv[1].lower()[0] == 'e' else '.wav' 557 | 558 | if sys.argv[2] == output_filename: 559 | print('Error: input and output files share the same name.', 560 | file=sys.stderr) 561 | return 1 562 | 563 | # Convert the file 564 | if sys.argv[1].lower()[0] == 'e': 565 | cursor = 0 566 | options = sys.argv[1].lower()[1:] 567 | ima_algorithm = False 568 | 569 | while cursor < len(options): 570 | if options[cursor] == 'i': 571 | ima_algorithm = True 572 | cursor += 1 573 | 574 | return encode_file(sys.argv[2], output_filename, ima_algorithm) 575 | 576 | elif sys.argv[1].lower()[0] == 'd': 577 | cursor = 0 578 | options = sys.argv[1].lower()[1:] 579 | ima_algorithm = False 580 | preserve_first = False 581 | 582 | while cursor < len(options): 583 | if options[cursor] == 'i': 584 | ima_algorithm = True 585 | elif options[cursor] == '1': 586 | preserve_first = True 587 | cursor += 1 588 | 589 | return decode_file(sys.argv[2], output_filename, ima_algorithm, \ 590 | preserve_first) 591 | 592 | 593 | if __name__ == '__main__': 594 | main() 595 | --------------------------------------------------------------------------------