# Overview A song or sound can be composed of multiple instruments or channels. The NES has 5 channels: 2 pulse (square wave) channels, 1 triangle channel, 1 noise channel, and 1 delta modulation channel. _Contra_ maintains 6 slots of data in memory that are used in priority order to play sounds. Higher slots are played before the lower slots. Each slot is linked to an NES sound channel. * #$00 = pulse 1 channel * #$01 = pulse 2 channel * #$02 = triangle channel * #$03 = noise and dmc channel * #$04 = pulse 1 * #$05 = noise channel For example, if a sound is loaded in slot #$00 and slot #$04 (both pulse 1 channel), then the sound in slot #$04 will be played since it is higher. The code that converts from sound slot to channel is `@load_sound_channel_offset`. When the game loads a sound to play, it first determines which slots are needed by loading data from `sound_table_00`. This table specifies the number of slots needed to play the sound and where the instructions to play the sound exists, i.e. the 2 byte cpu address where the sound channel instructions are. # Sound Codes Below is a table of all the sounds that exist in _Contra_, including unused sounds. The Japanese names were obtained from the "sound mode" feature in the Famicom version of the game. Each sound is related to one or more sound codes. For example, the level 3 waterfall music uses 4 sound codes, which means that sound uses 4 sound channels. At the bottom of the table are the DPCM samples that are used throughout the game by various other sounds. | Sound | Japanese Name | Description | sound_code(s) | Slot | Command Type | Channel | |-------|---------------|------------------------------------------------------|---------------|------|--------------|------------------| | #$01 | | empty/silence, used to initialize channel | `sound_01` | | | | | #$02 | | percussive tick (bass drum/tom drum) | `sound_02` | 5 | low | noise | | #$03 | FOOT | player landing on ground or water | `sound_03` | 4 | low | pulse 1 | | | | | `sound_04` | 5 | low | noise | | #$05 | ROCK | waterfall rock landing on ground | `sound_05` | 4 | low | pulse 1 | | #$06 | TYPE 1 | unused, keyboard typing in Japanese version of game | `sound_06` | 4 | low | pulse 1 | | | | | `sound_07` | 5 | low | noise | | #$08 | | unused, rumbling | `sound_08` | 5 | low | noise | | #$09 | FIRE | energy zone fire beam | `sound_09` | 5 | low | noise | | #$0a | SHOTGUN1 | default weapon | `sound_0a` | 4 | low | pulse 1 | | | | | `sound_0b` | 5 | low | noise | | #$0c | SHOTGUN2 | M weapon, turret man | `sound_0c` | 4 | low | pulse 1 | | | | | `sound_0d` | 5 | low | noise | | #$0e | LASER | L weapon | `sound_0e` | 4 | low | pulse 1 | | | | | `sound_0f` | 5 | low | noise | | #$10 | PL FIRE | F weapon | `sound_10` | 4 | low | pulse 1 | | | | | `sound_11` | 5 | low | noise | | #$12 | SPREAD | S weapon | `sound_12` | 4 | low | pulse 1 | | | | | `sound_13` | 5 | low | noise | | #$14 | HIBIWARE | bullet shielded wall plating ting | `sound_14` | 4 | low | pulse 1 | | #$15 | CHAKUCHI | energy zone boss landing | `sound_15` | 4 | low | pulse 1 | | #$16 | DAMEGE 1 | bullet to metal collision ting | `sound_16` | 4 | low | pulse 1 | | | | | `sound_17` | 5 | low | noise | | #$18 | DAMEGE 2 | alien heart boss hit | `sound_18` | 4 | low | pulse 1 | | #$19 | TEKI OUT | enemy destroyed | `sound_19` | 4 | low | pulse 1 | | #$1a | HIRAI 1 | ice grenade whistling noise | `sound_1a` | 4 | low | pulse 1 | | #$1b | SENSOR | level 1 jungle boss siren | `sound_1b` | 4 | low | pulse 1 | | #$1c | KANDEN | electrocution sound | `sound_1c` | 4 | low | pulse 1 | | | | | `sound_1d` | 5 | low | noise | | #$1e | CAR | tank advancing | `sound_1e` | 4 | low | noise | | #$1f | POWER UP | pick up weapon item | `sound_1f` | 4 | low | pulse 1 | | #$20 | 1UP | extra life | `sound_20` | 4 | low | pulse 1 | | #$21 | HERI | helicopter rotors | `sound_21` | 4 | low | pulse 1 | | | | | `sound_21` | 1 | low | pulse 2 | | | | | `sound_23` | 5 | low | noise | | #$24 | BAKUHA 1 | explosion | `sound_24` | 5 | low | noise | | #$25 | BAKUHA 2 | game intro, indoor wall, and island explosion | `sound_25` | 5 | low | noise | | #$26 | TITLE | game intro tune | `sound_26` | 0 | high | pulse 1 | | | | | `sound_27` | 1 | high | pulse 2 | | | | | `sound_28` | 2 | high | triangle | | | | | `sound_29` | 3 | high | noise | | #$2a | BGM 1 | level 1 jungle and level 7 hangar music | `sound_2a` | 0 | high | pulse 1 | | | | | `sound_2b` | 1 | high | pulse 2 | | | | | `sound_2c` | 2 | high | triangle | | | | | `sound_2d` | 3 | high | noise | | #$2e | BGM 2 | level 3 waterfall music | `sound_2e` | 0 | high | pulse 1 | | | | | `sound_2f` | 1 | high | pulse 2 | | | | | `sound_30` | 2 | high | triangle | | | | | `sound_31` | 3 | high | noise | | #$32 | BGM 3 | level 5 snow field music | `sound_32` | 0 | high | pulse 1 | | | | | `sound_33` | 1 | high | pulse 2 | | | | | `sound_34` | 2 | high | triangle | | | | | `sound_35` | 3 | high | noise | | #$36 | BGM 4 | level 6 energy zone | `sound_36` | 0 | high | pulse 1 | | | | | `sound_37` | 1 | high | pulse 2 | | | | | `sound_38` | 2 | high | triangle | | | | | `sound_39` | 3 | high | noise | | #$3a | BGM 5 | level 8 alien's lair music | `sound_3a` | 0 | high | pulse 1 | | | | | `sound_3b` | 1 | high | pulse 2 | | | | | `sound_3c` | 2 | high | triangle | | | | | `sound_3d` | 3 | high | noise | | #$3e | 3D BGM | indoor/base level music | `sound_3e` | 0 | high | pulse 1 | | | | | `sound_3f` | 1 | high | pulse 2 | | | | | `sound_40` | 2 | high | triangle | | | | | `sound_41` | 3 | high | noise | | #$42 | BOSS | indoor/base boss screen music | `sound_42` | 0 | high | pulse 1 | | | | | `sound_43` | 1 | high | pulse 2 | | | | | `sound_44` | 2 | high | triangle | | | | | `sound_45` | 3 | high | noise | | #$46 | PCLR | end of level tune | `sound_46` | 0 | high | pulse 1 | | | | | `sound_47` | 1 | high | pulse 2 | | | | | `sound_48` | 2 | high | triangle | | | | | `sound_49` | 3 | high | noise | | #$4a | ENDING | end credits | `sound_4a` | 0 | high | pulse 1 | | | | | `sound_4b` | 1 | high | pulse 2 | | | | | `sound_4c` | 2 | high | triangle | | | | | `sound_4d` | 3 | high | noise | | #$4e | OVER | game over/after end credits, presented by Konami | `sound_4e` | 0 | high | pulse 1 | | | | | `sound_4f` | 1 | high | pulse 2 | | | | | `sound_50` | 2 | high | triangle | | | | | `sound_51` | 3 | high | noise | | #$52 | PL OUT | player death | `sound_52` | 4 | low | pulse 1 | | | | | `sound_53` | 5 | low | noise | | #$54 | | game pausing | `sound_54` | 4 | low | pulse 1 | | #$55 | BOSS BK | tank, boss ufo, boss giant, alien guardian destroyed | `sound_55` | 4 | low | pulse 1 | | | | | `sound_56` | 5 | low | noise | | #$57 | BOSS OUT | boss destroyed | `sound_57` | 4 | low | pulse 1 | | | | | `sound_58` | 1 | low | pulse 2 | | | | | `sound_59` | 5 | low | noise | | #$5a | n/a | high hat | n/a | n/a | low | delta modulation | | #$5b | n/a | snare | n/a | n/a | low | delta modulation | | #$5c | n/a | high hat | n/a | n/a | low | delta modulation | | #$ff | n/a | snowfield boss defeated door open (bug) | n/a | n/a | low | delta modulation | The sound for pausing the game is not in the sound mode menu, presumably to not confuse players into thinking the game is paused. As for names, I can guess at some of the abbreviations and name meanings. * BAKUHA - ばくはつ (爆発) - Japanese for explosion * BGM - background music * BK - BAKUHA, i.e. explosion * CHAKUCHI - ちゃくち (着地) - Japanese for landing/touching the ground * HIBIWARE - ひびわれ (罅割れ, ひび割れ) - Japanese for crack; crevice; fissure * PCLR - player clear, or pattern clear * PL - player * TYPE - keyboard typing # sound_code Parsing Every video frame, the game loops through each sound slot to see if a sound is currently playing, see `@sound_slot_loop`. If a sound slot is populated, i.e. a sound is playing, then `handle_sound_code` will be called on that slot. `handle_sound_code` will first check if the game is paused, as the music and sound effects are paused when the game is paused. If the game isn't paused, the `handle_sound_code` will decrement the current sound slot's sound length (`SOUND_CMD_LENGTH`) and if the sound is finished, move to read the next command (`read_sound_command_00`). If the sound isn't finished, then `@pulse_vol_and_vibrato` is called to possibly adjust the volume and frequency of the current playing sound. Note frequency adjustments, i.e. vibrato isn't used by _Contra_. A `sound_code` is composed of 1 or more sound commands. Each sound command will configure variables or set APU registers. Not every sound command will make a sound. Some just configure variables for the subsequent sound code, for example setting `SOUND_LENGTH_MULTIPLIER` for use by a subsequent sound command. The sound commands are parsed according to the following logic. The first byte of the entire sound code determines how the rest of the commands for the sound code will be parsed. When the byte is less than #$30, the the sound code is considered a 'low sound' code. Otherwise, the code will be parsed as a 'high sound' code. ## sound_code Sharing All sound command types can reference addresses to other sound commands. When a sound command moves to another command, once that command is finished executing, then the sound read pointer goes back to the next bytes in the original command. * #$fd - move to child sound command for playing shared sound data across different `sound_xx` commands, or shared parts within the same sound code. Move to execute sound command at address specified by next 2 bytes. * #$fe - repeat the next sound command at address `a` `n` times, where `a` is the next byte and `n` is the 2nd and 3rd byte. * #$ff - finished reading sound command, exit to previous command if child sound command, otherwise, finished entire sound code ## Low Sound Command In _Contra_, low sound commands are used by sound slots #$01 (pulse 1), #$04 (pulse 2) and #$05 (noise). Low sound commands are used for sound effects. The method in code for parsing low sound commands is `read_low_sound_cmd`. In general, low sound commands set the length, decrescendo start, pitch, and duty. The first byte of the sound command dictates how the subsequent bytes are interpreted. The command is read recursively until the note period is set, then the parsing exits. * **Case 1** - #$2x - sets the number of video frames to wait before reading the next sound command (`SOUND_LENGTH_MULTIPLIER`) as well as the high nibble of the APU configuration register for the sound channel (`SOUND_CFG_HIGH`). * when low nibble is not #$f, then `SOUND_LENGTH_MULTIPLIER` is set to low nibble. * when low nibble is #$f, then the next full byte is used to set `SOUND_LENGTH_MULTIPLIER` * the following byte is then used to set high nibble of the APU configuration register for the sound channel (`SOUND_CFG_HIGH`) * **Case 2** - #$10 - enable/disable sweep and set volume decrescendo. What is set is based on the next byte. Additionally, if the sound slot is #$04, then the pulse 1 channel `PULSE_VOL_DURATION` will also be set to the sweep value. * non-zero - sweep will be enabled and set to the value of the byte. * #$00 - if the byte after #$10 is #$00, then sweep will be disabled by setting the APU register $4001 to #$7f. be set to #$7f. * **Case 3** - #$1x - slightly flatten the note that will be played by less than 1Hz by setting bit 4 of `SOUND_FLAGS`. The low nibble is not used. This case is not used in _Contra_. However, notes are flattened in another flow, see `@flip_flatten_note_adv`. * **Case 4** - #$xx - if not #2x, or #$1x, then byte high nibble is used as the low nibble (volume) for APU channel config. The high nibble and low nibble from memory are merged (`SOUND_CFG_HIGH` and `SOUND_CFG_LOW` respectively), unless volume is constant, then only the high nibble is used when setting the sound channel configuration ($4000). Also, the sound length (`SOUND_CMD_LENGTH`), value is set based on `SOUND_LENGTH_MULTIPLIER`. Finally, the note frequency/counter/pitch is set based off the next two bytes and then the parsing is complete. After case 4 is parsed, the read low sound command method exits. Then the game logic will continue. The next frame, a standard `@sound_slot_loop` will pick up the sound slot is populated and play possibly modify the sound's volume and frequency for vibrato (not used in _Contra_). Every video frame, the game logic repeats this until the sound is completed, the game logic will then continue by reading the next byte of the next low sound code. This entire process repeats until a #$ff is read. ## High Sound Command In _Contra_, high sound commands are used by sound slots #$00 (pulse 1), #$01 (pulse 2), #$02 (triangle) and #$03 (noise & dmc channel). High sound commands are used for the 'music' of the game: the level background music, the intro tune, end credits song, and after credits/game over music. The method in code for parsing low sound commands is `read_high_sound_cmd`. The first byte of the sound command dictates how the subsequent bytes are interpreted. * **Case 1** - if the sound slot is #$03 (noise and dmc channel), then `parse_percussion_cmd` is called to handle the percussion. See notes below in section titled `Percussion Command`. * **Case 2** - if byte 0 high nibble is less than #$c, then `simple_sound_cmd` is used. This plays a single note with a specified length change from previous note with a volume envelope specified by `lvl_config_pulse`. * **Case 3** - if byte 0 high nibble is greater than or equal to #$0c, then `@regular_sound_cmd` is used. `@regular_sound_cmd` looks at bits 4 and 5 to know which entry in `sound_cmd_ptr_tbl` to use to handle the sound command. For details of what each method does, see section `sound_cmd_routine_xx`. ### sound_cmd_routine_xx * `sound_cmd_routine_00` - sets sound channel config to mute, marks channel as muted by setting bit 6 of `SOUND_FLAGS`. * `sound_cmd_routine_01` - sets sound length multiplier (`SOUND_LENGTH_MULTIPLIER`) to low nibble. Sets in memory low nibble of sound channel. Can initialize channel by calling `exe_channel_init_ptr_tbl_routine`. Otherwise, recursively calls back to `read_high_sound_cmd` to handle next byte. * `sound_cmd_routine_02` * If the low nibble is less than #$5, then sets note adjustment flag (`SOUND_PERIOD_ROTATE`) and recursively calls back to `read_high_sound_cmd`. * If the low nibble is #$8, set bit 4 of `SOUND_FLAGS` to mark note as slightly flattened from original value. * If the low nibble is #$b, set vibrato variables and recursively call `read_high_sound_cmd`. * If the low nibble is #$c, set the pitch based on next sound byte, which (when doubled) is an offset into `note_period_tbl`. * If the low nibble isn't any known value, just ignore it and recursively call `read_high_sound_cmd`. * `sound_cmd_routine_03` - this function handles the end of a sound command and determines where to go next based on the byte value. See section above titled `sound_code Sharing`. ### Percussion Command Percussion commands are recursively read until Case 1 or Case 3 is reached. * **Case 1** - The byte's high nibble is #$f. This is an end of sound command, the sound command will either end, repeat, or move back to parent sound command. See the section titled `sound_code Sharing`. * **Case 2** - The byte's high nibble is #$d. The low nibble is used to set the sound length multiplier (`SOUND_LENGTH_MULTIPLIER`). * **Case 3** - The byte high nibble isn't #$f, nor #$d. In this case, call `calc_cmd_len_play_percussion` to determine sound command length (`SOUND_CMD_LENGTH`) based on the low nibble and the value determined from Case 2. Then call `play_percussive_sound`. This method will use the high nibble (shifted into low nibble) of the byte value to get offset into `percussion_tbl`, which specifies which DMC sound sample code to play. This value is passed to `play_sound` to play the sound code. Then, if the value was greater than or equal to #$3, `sound_02` is also played with other sound code. The offsets are defined and which sound(s) is/are played are below. Note that in offset 5, `sound_02` is not played because there is a check in `load_sound_code_entry` and `sound_25` is already playing in slot #$05. * 0 - `sound_02` * 1 - `sound_5a` * 2 - `sound_5b` * 3 - `sound_5a` and `sound_02` * 4 - `sound_5b` and `sound_02` * 5 - `sound_25` * 6 - `sound_5c` and `sound_02` * 7 - `sound_5d` and `sound_02`