Skip to navigation


Bank 6 (Part 1 of 3)

[NES version]

NES ELITE GAME SOURCE (BANK 6) NES Elite was written by Ian Bell and David Braben and is copyright D. Braben and I. Bell 1991/1992 The sound player in this bank was written by David Whittaker The code on this site has been reconstructed from a disassembly of the version released on Ian Bell's personal website at http://www.elitehomepage.org/ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes in the documentation are entirely my fault The terminology and notations used in this commentary are explained at https://elite.bbcelite.com/terminology The deep dive articles referred to in this commentary can be found at https://elite.bbcelite.com/deep_dives
This source file produces the following binary file: * bank6.bin
ELITE BANK 1 Produces the binary file bank1.bin.
ORG CODE%
Name: ResetMMC1_b6 [Show more] Type: Subroutine Category: Start and end Summary: The MMC1 mapper reset routine at the start of the ROM bank Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * Vectors_b6 calls ResetMMC1_b6

When the NES is switched on, it is hardwired to perform a JMP ($FFFC). At this point, there is no guarantee as to which ROM banks are mapped to $8000 and $C000, so to ensure that the game starts up correctly, we put the same code in each ROM at the following locations: * We put $C000 in address $FFFC in every ROM bank, so the NES always jumps to $C000 when it starts up via the JMP ($FFFC), irrespective of which ROM bank is mapped to $C000. * We put the same reset routine (this routine, ResetMMC1) at the start of every ROM bank, so the same routine gets run, whichever ROM bank is mapped to $C000. This ResetMMC1 routine is therefore called when the NES starts up, whatever the bank configuration ends up being. It then switches ROM bank 7 to $C000 and jumps into bank 7 at the game's entry point BEGIN, which starts the game.
.ResetMMC1_b6 SEI ; Disable interrupts INC $C006 ; Reset the MMC1 mapper, which we can do by writing a ; value with bit 7 set into any address in ROM space ; (i.e. any address from $8000 to $FFFF) ; ; The INC instruction does this in a more efficient ; manner than an LDA/STA pair, as it: ; ; * Fetches the contents of address $C006, which ; contains the high byte of the JMP destination ; below, i.e. the high byte of BEGIN, which is $C0 ; ; * Adds 1, to give $C1 ; ; * Writes the value $C1 back to address $C006 ; ; $C006 is in the ROM space and $C1 has bit 7 set, so ; the INC does all that is required to reset the mapper, ; in fewer cycles and bytes than an LDA/STA pair ; ; Resetting MMC1 maps bank 7 to $C000 and enables the ; bank at $8000 to be switched, so this instruction ; ensures that bank 7 is present JMP BEGIN ; Jump to BEGIN in bank 7 to start the game
Name: Interrupts_b6 [Show more] Type: Subroutine Category: Start and end Summary: The IRQ and NMI handler while the MMC1 mapper reset routine is still running
Context: See this subroutine on its own page References: This subroutine is called as follows: * Vectors_b6 calls Interrupts_b6
.Interrupts_b6 IF _NTSC RTI ; Return from the IRQ interrupt without doing anything ; ; This ensures that while the system is starting up and ; the ROM banks are in an unknown configuration, any IRQ ; interrupts that go via the vector at $FFFE and any NMI ; interrupts that go via the vector at $FFFA will end up ; here and be dealt with ; ; Once bank 7 is switched into $C000 by the ResetMMC1 ; routine, the vector is overwritten with the last two ; bytes of bank 7, which point to the IRQ routine ENDIF
Name: versionNumber_b6 [Show more] Type: Variable Category: Text Summary: The game's version number in bank 6
Context: See this variable on its own page References: No direct references to this variable in this source file
IF _NTSC EQUS " 5.0" ELIF _PAL EQUS "<2.8>" ENDIF
Name: ChooseMusicS [Show more] Type: Subroutine Category: Sound Summary: A jump table entry at the start of bank 6 for the ChooseMusic routine
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.ChooseMusicS JMP ChooseMusic ; Jump to the ChooseMusic routine, returning from the ; subroutine using a tail call
Name: MakeSoundsS [Show more] Type: Subroutine Category: Sound Summary: A jump table entry at the start of bank 6 for the MakeSounds routine
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.MakeSoundsS JMP MakeSounds ; Jump to the MakeSounds routine, returning from the ; subroutine using a tail call
Name: StopSoundsS [Show more] Type: Subroutine Category: Sound Summary: A jump table entry at the start of bank 6 for the StopSounds routine
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChooseMusic calls StopSoundsS * MakeMusicOnNOISE calls StopSoundsS * MakeMusicOnSQ1 calls StopSoundsS * MakeMusicOnSQ2 calls StopSoundsS * MakeMusicOnTRI calls StopSoundsS * StopSounds_b6 calls StopSoundsS
.StopSoundsS JMP StopSounds ; Jump to the StopSounds routine, returning from the ; subroutine using a tail call
Name: EnableSoundS [Show more] Type: Subroutine Category: Sound Summary: A jump table entry at the start of bank 6 for the EnableSound routine
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.EnableSoundS JMP EnableSound ; Jump to the EnableSound routine, returning from the ; subroutine using a tail call
Name: StartEffectOnSQ1S [Show more] Type: Subroutine Category: Sound Summary: A jump table entry at the start of bank 6 for the StartEffectOnSQ1 routine
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.StartEffectOnSQ1S JMP StartEffectOnSQ1 ; Jump to the StartEffectOnSQ1 routine, returning from ; the subroutine using a tail call
Name: StartEffectOnSQ2S [Show more] Type: Subroutine Category: Sound Summary: A jump table entry at the start of bank 6 for the StartEffectOnSQ2 routine
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.StartEffectOnSQ2S JMP StartEffectOnSQ2 ; Jump to the StartEffectOnSQ2 routine, returning from ; the subroutine using a tail call
Name: StartEffectOnNOISES [Show more] Type: Subroutine Category: Sound Summary: A jump table entry at the start of bank 6 for the StartEffectOnNOISE routine
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.StartEffectOnNOISES JMP StartEffectOnNOISE ; Jump to the StartEffectOnNOISE routine, returning from ; the subroutine using a tail call
Name: ChooseMusic [Show more] Type: Subroutine Category: Sound Summary: Set the tune for the background music
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChooseMusic_b6 calls ChooseMusic * ChooseMusicS calls ChooseMusic

The tune numbers are as follows: * 0 for the title music ("Elite Theme"), which is set in the TITLE routine and as the default tune in the ResetMusic routine * 1 for docking ("The Blue Danube"), which is set in the TT102 routine * 2 for the combat demo music ("Game Theme"), though this is never set directly, only via tune 4 * 3 for the scroll text music ("Assassin's Touch"), though this is never set directly, only via tune 4 * 4 for the full combat demo suite ("Assassin's Touch" followed by "Game Theme"), which is set in the DEATH2 routine
Arguments: A The number of the tune to choose
.ChooseMusic TAY ; Set Y to the tune number JSR StopSoundsS ; Call StopSounds via StopSoundsS to stop all sounds ; (both music and sound effects) ; ; This also sets enableSound to 0 ; We now calculate the offset into the tuneData table ; for this tune\s data, which will be Y * 9 as there are ; nine bytes for each tune at the start of the table LDA #0 ; Set A = 0 so we can build the results of the ; calculation by adding 9 to A, Y times CLC ; Clear the C flag for the addition below .cmus1 DEY ; Decrement the tune number in Y BMI cmus2 ; If the result is negative then A contains the result ; of Y * 9, so jump to cmus2 ADC #9 ; Set A = A * 9 BNE cmus1 ; Loop back to cmus1 to add another 9 to A (this BNE is ; effectively a JMP as A is never zero) .cmus2 TAX ; Copy the result into X, so X = tune number * 9, which ; we can use as the offset into the tuneData table ; below ; We now reset the four 19-byte blocks of memory that ; are used to store the channel-specific variables, as ; follows: ; ; sectionDataSQ1 to applyVolumeSQ1 ; sectionDataSQ2 to applyVolumeSQ2 ; sectionDataTRI to volumeEnvelopeTRI+1 ; sectionDataNOISE to applyVolumeNOISE ; ; There is no volumeEnvelopeTRI variable but the space ; is still reserved, which is why the TRI channel clears ; to volumeEnvelopeTRI+1 LDA #0 ; Set A = 0 to use when zeroing these locations LDY #18 ; Set a counter in Y for the 19 bytes in each block .cmus3 STA sectionDataSQ1,Y ; Zero the Y-th byte of sectionDataSQ1 STA sectionDataSQ2,Y ; Zero the Y-th byte of sectionDataSQ2 STA sectionDataTRI,Y ; Zero the Y-th byte of sectionDataTRI STA sectionDataNOISE,Y ; Zero the Y-th byte of sectionDataNOISE DEY ; Decrement the loop counter in Y BPL cmus3 ; Loop back until we have zeroed bytes 0 to 18 in all ; four blocks TAY ; Set Y = 0, to use as an index when fetching addresses ; from the tuneData table LDA tuneData,X ; Fetch the first byte from the tune's block at STA tuneSpeed ; tuneData, which contains the tune's speed, and store STA tuneSpeedCopy ; it in tuneSpeed and tuneSpeedCopy ; ; For tune 0, this would be 47 LDA tuneData+1,X ; Set soundAddr(1 0) and sectionListSQ1(1 0) to the STA sectionListSQ1 ; first address from the tune's block at tuneData STA soundAddr ; LDA tuneData+2,X ; For tune 0, this would set both variables to point to STA sectionListSQ1+1 ; the list of tune sections at tune0Data_SQ1 STA soundAddr+1 LDA (soundAddr),Y ; Fetch the address that the first address points to STA sectionDataSQ1 ; and put it in sectionDataSQ1(1 0), incrementing the INY ; index in Y in the process LDA (soundAddr),Y ; STA sectionDataSQ1+1 ; For tune 0, this would set sectionDataSQ1(1 0) to the ; address of tune0Data_SQ1_0 LDA tuneData+3,X ; Set soundAddr(1 0) and sectionListSQ2(1 0) to the STA sectionListSQ2 ; second address from the tune's block at tuneData STA soundAddr ; LDA tuneData+4,X ; For tune 0, this would set both variables to point to STA sectionListSQ2+1 ; the list of tune sections at tune0Data_SQ2 STA soundAddr+1 DEY ; Decrement the index in Y, so it is zero once again LDA (soundAddr),Y ; Fetch the address that the second address points to STA sectionDataSQ2 ; and put it in sectionDataSQ2(1 0), incrementing the INY ; index in Y in the process LDA (soundAddr),Y ; STA sectionDataSQ2+1 ; For tune 0, this would set sectionDataSQ2(1 0) to the ; address of tune0Data_SQ2_0 LDA tuneData+5,X ; Set soundAddr(1 0) and sectionListTRI(1 0) to the STA sectionListTRI ; third address from the tune's block at tuneData STA soundAddr ; LDA tuneData+6,X ; For tune 0, this would set both variables to point to STA sectionListTRI+1 ; the list of tune sections at tune0Data_TRI STA soundAddr+1 DEY ; Decrement the index in Y, so it is zero once again LDA (soundAddr),Y ; Fetch the address that the third address points to STA sectionDataTRI ; and put it in sectionDataTRI(1 0), incrementing the INY ; index in Y in the process LDA (soundAddr),Y ; STA sectionDataTRI+1 ; For tune 0, this would set sectionDataTRI(1 0) to the ; address of tune0Data_TRI_0 LDA tuneData+7,X ; Set soundAddr(1 0) and sectionListNOISE(1 0) to the STA sectionListNOISE ; fourth address from the tune's block at tuneData STA soundAddr ; LDA tuneData+8,X ; For tune 0, this would set both variables to point to STA sectionListNOISE+1 ; the list of tune sections at tune0Data_NOISE STA soundAddr+1 DEY ; Decrement the index in Y, so it is zero once again LDA (soundAddr),Y ; Fetch the address that the fourth address points to STA sectionDataNOISE ; and put it in sectionDataNOISE(1 0), incrementing the INY ; index in Y in the process LDA (soundAddr),Y ; STA sectionDataNOISE+1 ; For tune 0, this would set sectionDataNOISE(1 0) to ; the address of tune0Data_NOISE_0 STY pauseCountSQ1 ; Set pauseCountSQ1 = 1 so we start sending music to the ; SQ1 channel straight away, without a pause STY pauseCountSQ2 ; Set pauseCountSQ2 = 1 so we start sending music to the ; SQ2 channel straight away, without a pause STY pauseCountTRI ; Set pauseCountTRI = 1 so we start sending music to the ; TRI channel straight away, without a pause STY pauseCountNOISE ; Set pauseCountNOISE = 1 so we start sending music to ; the NOISE channel straight away, without a pause INY ; Increment Y to 2 STY nextSectionSQ1 ; Set nextSectionSQ1(1 0) = 2 (the high byte was already ; zeroed above), so the next section after the first on ; the SQ1 channel is the second section STY nextSectionSQ2 ; Set nextSectionSQ2(1 0) = 2 (the high byte was already ; zeroed above), so the next section after the first on ; the SQ2 channel is the second section STY nextSectionTRI ; Set nextSectionTRI(1 0) = 2 (the high byte was already ; zeroed above), so the next section after the first on ; the TRI channel is the second section STY nextSectionNOISE ; Set nextSectionNOISE = 2 (the high byte was already ; zeroed above), so the next section after the first on ; the NOISE channel is the second section LDX #0 ; Set tuningAll = 0 to set all channels to the default STX tuningAll ; tuning DEX ; Decrement X to $FF STX tuneProgress ; Set tuneProgress = $FF, so adding any non-zero speed ; at the start of MakeMusic will overflow the progress ; counter and start playing the music straight away STX playMusic ; Set playMusic = $FF to enable the new tune to be ; played INC enableSound ; Increment enableSound to 1 to enable sound, now that ; we have set up the music to play RTS ; Return from the subroutine
Name: EnableSound [Show more] Type: Subroutine Category: Sound Summary: Enable sounds (music and sound effects)
Context: See this subroutine on its own page References: This subroutine is called as follows: * EnableSoundS calls EnableSound
.EnableSound LDA playMusic ; If playMusic = 0 then the music has been disabled by BEQ enas1 ; note command $FE, so jump to enas1 to leave the value ; of enableSound alone and return from the subroutine ; as only a new call to ChooseMusic can enable the music ; again LDA enableSound ; If enableSound is already non-zero, jump to enas1 to BNE enas1 ; leave it and return from the subroutine INC enableSound ; Otherwise increment enableSound from 0 to 1 .enas1 RTS ; Return from the subroutine
Name: StopSounds [Show more] Type: Subroutine Category: Sound Summary: Stop all sounds (music and sound effects)
Context: See this subroutine on its own page References: This subroutine is called as follows: * StopSoundsS calls StopSounds
.StopSounds LDA #0 ; Set enableSound = 0 to disable all sounds (music and STA enableSound ; sound effects) STA effectOnSQ1 ; Set effectOnSQ1 = 0 to indicate the SQ1 channel is ; clear of sound effects STA effectOnSQ2 ; Set effectOnSQ2 = 0 to indicate the SQ2 channel is ; clear of sound effects STA effectOnNOISE ; Set effectOnNOISE = 0 to indicate the NOISE channel is ; clear of sound effects TAX ; We now clear the 16 bytes at sq1Volume, so set X = 0 ; to act as an index in the following loop .stop1 STA sq1Volume,X ; Zero the X-th byte of sq1Volume INX ; Increment the index counter CPX #16 ; Loop back until we have cleared all 16 bytes BNE stop1 STA TRI_LINEAR ; Zero the linear counter for the TRI channel, which ; configures it as follows: ; ; * Bit 7 clear = do not reload the linear counter ; ; * Bits 0-6 = counter reload value of 0 ; ; So this silences the TRI channel LDA #%00110000 ; Set the volume of the SQ1, SQ2 and NOISE channels to STA SQ1_VOL ; zero as follows: STA SQ2_VOL ; STA NOISE_VOL ; * Bits 6-7 = duty pulse length is 3 ; * Bit 5 set = infinite play ; * Bit 4 set = constant volume ; * Bits 0-3 = volume is 0 LDA #%00001111 ; Enable the sound channels by writing to the sound STA SND_CHN ; status register in SND_CHN as follows: ; ; Bit 4 clear = disable the DMC channel ; Bit 3 set = enable the NOISE channel ; Bit 2 set = enable the TRI channel ; Bit 1 set = enable the SQ2 channel ; Bit 0 set = enable the SQ1 channel RTS ; Return from the subroutine
Name: MakeSounds [Show more] Type: Subroutine Category: Sound Summary: Make the current sounds (music and sound effects) Deep dive: Sound effects in NES Elite Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeSounds_b6 calls MakeSounds * MakeSoundsS calls MakeSounds
.MakeSounds JSR MakeMusic ; Calculate the current music on the SQ1, SQ2, TRI and ; NOISE channels JSR MakeSound ; Calculate the current sound effects on the SQ1, SQ2 ; and NOISE channels LDA enableSound ; If enableSound = 0 then sound is disabled, so jump to BEQ maks3 ; maks3 to return from the subroutine LDA effectOnSQ1 ; If effectOnSQ1 is non-zero then a sound effect is BNE maks1 ; being made on channel SQ1, so jump to maks1 to skip ; writing the music data to the APU (so sound effects ; take precedence over music) LDA sq1Volume ; Send sq1Volume to the APU via SQ1_VOL STA SQ1_VOL LDA sq1Sweep ; If sq1Sweep is non-zero then there is a sweep unit in BNE maks1 ; play on channel SQ1, so jump to maks1 to skip the ; following as the sweep will take care of the pitch LDA sq1Lo ; Otherwise send sq1Lo to the APU via SQ1_LO to set the STA SQ1_LO ; pitch on channel SQ1 .maks1 LDA effectOnSQ2 ; If effectOnSQ2 is non-zero then a sound effect is BNE maks2 ; being made on channel SQ2, so jump to maks2 to skip ; writing the music data to the APU (so sound effects ; take precedence over music) LDA sq2Volume ; Send sq2Volume to the APU via SQ2_VOL STA SQ2_VOL LDA sq2Sweep ; If sq2Sweep is non-zero then there is a sweep unit in BNE maks2 ; play on channel SQ2, so jump to maks2 to skip the ; following as the sweep will take care of the pitch LDA sq2Lo ; Otherwise send sq2Lo to the APU via SQ2_LO to set the STA SQ2_LO ; pitch on channel SQ2 .maks2 LDA triLo ; Send triLo to the APU via TRI_LO STA TRI_LO LDA effectOnNOISE ; If effectOnNOISE is non-zero then a sound effect is BNE maks3 ; being made on channel NOISE, so jump to maks3 to skip ; writing the music data to the APU (so sound effects ; take precedence over music) LDA noiseVolume ; Send noiseVolume to the APU via NOISE_VOL STA NOISE_VOL LDA noiseLo ; Send noiseLo to the APU via NOISE_LO STA NOISE_LO .maks3 RTS ; Return from the subroutine
Name: MakeMusic [Show more] Type: Subroutine Category: Sound Summary: Play the current music on the SQ1, SQ2, TRI and NOISE channels Deep dive: Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeSounds calls MakeMusic
.MakeMusic LDA enableSound ; If enableSound is non-zero then sound is enabled, so BNE makm1 ; jump to makm1 to play the current music RTS ; Otherwise sound is disabled, so return from the ; subroutine .makm1 LDA tuneSpeed ; Set tuneProgress = tuneProgress + tuneSpeed CLC ; ADC tuneProgress ; This moves the tune along by the current speed, STA tuneProgress ; setting the C flag only when the addition of this ; iteration's speed overflows the addition ; ; This ensures that we only send music to the APU once ; every 256 / tuneSpeed iterations, which keeps the ; music in sync and sends the music more regularly with ; higher values of tuneSpeed BCC makm2 ; If the addition didn't overflow, jump to makm2 to skip ; playing music in this VBlank JSR MakeMusicOnSQ1 ; Play the current music on the SQ1 channel JSR MakeMusicOnSQ2 ; Play the current music on the SQ2 channel JSR MakeMusicOnTRI ; Play the current music on the TRI channel JSR MakeMusicOnNOISE ; Play the current music on the NOISE channel .makm2 JSR ApplyEnvelopeSQ1 ; Apply volume and pitch changes to the SQ1 channel JSR ApplyEnvelopeSQ2 ; Apply volume and pitch changes to the SQ2 channel JSR ApplyEnvelopeTRI ; Apply volume and pitch changes to the TRI channel JMP ApplyEnvelopeNOISE ; Apply volume and pitch changes to the NOISE channel, ; returning from the subroutine using a tail call
Name: MakeMusicOnSQ1 [Show more] Type: Subroutine Category: Sound Summary: Play the current music on the SQ1 channel Deep dive: Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeMusic calls MakeMusicOnSQ1
.MakeMusicOnSQ1 DEC pauseCountSQ1 ; Decrement the sound counter for SQ1 BEQ muso1 ; If the counter has reached zero, jump to muso1 to make ; music on the SQ1 channel RTS ; Otherwise return from the subroutine .muso1 LDA sectionDataSQ1 ; Set soundAddr(1 0) = sectionDataSQ1(1 0) STA soundAddr ; LDA sectionDataSQ1+1 ; So soundAddr(1 0) points to the note data for this STA soundAddr+1 ; part of the tune LDA #0 ; Set sq1Sweep = 0 STA sq1Sweep STA applyVolumeSQ1 ; Set applyVolumeSQ1 = 0 so we don't apply the volume ; envelope by default (this gets changed if we process ; note data below, as opposed to a command) .muso2 LDY #0 ; Set Y to the next entry from the note data LDA (soundAddr),Y TAY INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE muso3 ; in the note data INC soundAddr+1 .muso3 TYA ; Set A to the next entry that we just fetched from the ; note data BMI muso8 ; If bit 7 of A is set then this is a command byte, so ; jump to muso8 to process it CMP #$60 ; If the note data in A is less than $60, jump to muso4 BCC muso4 ADC #$A0 ; The note data in A is between $60 and $7F, so set the STA startPauseSQ1 ; following: ; ; startPauseSQ1 = A - $5F ; ; We know the C flag is set as we just passed through a ; BCC, so the ADC actually adds $A1, which is the same ; as subtracting $5F ; ; So this sets startPauseSQ1 to a value between 1 and ; 32, corresponding to note data values between $60 and ; $7F JMP muso2 ; Jump back to muso2 to move on to the next entry from ; the note data .muso4 ; If we get here then the note data in A is less than ; $60, which denotes a sound to send to the APU, so we ; now convert the data to a frequency and send it to the ; APU to make a sound on channel SQ1 CLC ; Set Y = (A + tuningAll + tuningSQ1) * 2 ADC tuningAll CLC ADC tuningSQ1 ASL A TAY LDA noteFrequency,Y ; Set (sq1Hi sq1Lo) the frequency for note Y STA sq1LoCopy ; STA sq1Lo ; Also save a copy of the low byte in sq1LoCopy LDA noteFrequency+1,Y STA sq1Hi LDX effectOnSQ1 ; If effectOnSQ1 is non-zero then a sound effect is BNE muso5 ; being made on channel SQ1, so jump to muso5 to skip ; writing the music data to the APU (so sound effects ; take precedence over music) LDX sq1Sweep ; Send sq1Sweep to the APU via SQ1_SWEEP STX SQ1_SWEEP LDX sq1Lo ; Send (sq1Hi sq1Lo) to the APU via SQ1_HI and SQ1_LO STX SQ1_LO STA SQ1_HI .muso5 LDA #1 ; Set volumeIndexSQ1 = 1 STA volumeIndexSQ1 LDA volumeRepeatSQ1 ; Set volumeCounterSQ1 = volumeRepeatSQ1 STA volumeCounterSQ1 .muso6 LDA #$FF ; Set applyVolumeSQ1 = $FF so we apply the volume STA applyVolumeSQ1 ; envelope in the next iteration .muso7 LDA soundAddr ; Set sectionDataSQ1(1 0) = soundAddr(1 0) STA sectionDataSQ1 ; LDA soundAddr+1 ; This updates the pointer to the note data for the STA sectionDataSQ1+1 ; channel, so the next time we can pick up where we left ; off LDA startPauseSQ1 ; Set pauseCountSQ1 = startPauseSQ1 STA pauseCountSQ1 ; ; So if startPauseSQ1 is non-zero (as set by note data ; the range $60 to $7F), the next startPauseSQ1 ; iterations of MakeMusicOnSQ1 will do nothing RTS ; Return from the subroutine .muso8 ; If we get here then bit 7 of the note data in A is ; set, so this is a command byte LDY #0 ; Set Y = 0, so we can use it in various commands below CMP #$FF ; If A is not $FF, jump to muso10 to check for the next BNE muso10 ; command ; If we get here then the command in A is $FF ; ; <$FF> moves to the next section in the current tune LDA nextSectionSQ1 ; Set soundAddr(1 0) to the following: CLC ; ADC sectionListSQ1 ; sectionListSQ1(1 0) + nextSectionSQ1(1 0) STA soundAddr ; LDA nextSectionSQ1+1 ; So soundAddr(1 0) points to the address of the next ADC sectionListSQ1+1 ; section in the current tune STA soundAddr+1 ; ; So if we are playing tune 2 and nextSectionSQ1(1 0) ; points to the second section, then soundAddr(1 0) ; will now point to the second address in tune2Data_SQ1, ; which itself points to the note data for the second ; section at tune2Data_SQ1_1 LDA nextSectionSQ1 ; Set nextSectionSQ1(1 0) = nextSectionSQ1(1 0) + 2 ADC #2 ; STA nextSectionSQ1 ; So nextSectionSQ1(1 0) now points to the next section, TYA ; as each section consists of two bytes in the table at ADC nextSectionSQ1+1 ; sectionListSQ1(1 0) STA nextSectionSQ1+1 LDA (soundAddr),Y ; If the address at soundAddr(1 0) is non-zero then it INY ; contains a valid address to the section's note data, ORA (soundAddr),Y ; so jump to muso9 to skip the following BNE muso9 ; ; This also increments the index in Y to 1 ; If we get here then the command is trying to move to ; the next section, but that section contains value of ; $0000 in the tuneData table, so there is no next ; section and we have reached the end of the tune, so ; instead we jump back to the start of the tune LDA sectionListSQ1 ; Set soundAddr(1 0) = sectionListSQ1(1 0) STA soundAddr ; LDA sectionListSQ1+1 ; So we start again by pointing soundAddr(1 0) to the STA soundAddr+1 ; first entry in the section list for channel SQ1, which ; contains the address of the first section's note data LDA #2 ; Set nextSectionSQ1(1 0) = 2 STA nextSectionSQ1 ; LDA #0 ; So the next section after we play the first section STA nextSectionSQ1+1 ; will be the second section .muso9 ; By this point, Y has been incremented to 1 LDA (soundAddr),Y ; Set soundAddr(1 0) to the address at soundAddr(1 0) TAX ; DEY ; As we pointed soundAddr(1 0) to the address of the LDA (soundAddr),Y ; new section above, this fetches the first address from STA soundAddr ; the new section's address list, which points to the STX soundAddr+1 ; new section's note data ; ; So soundAddr(1 0) now points to the note data for the ; new section, so we're ready to start processing notes ; and commands when we rejoin the muso2 loop JMP muso2 ; Jump back to muso2 to start processing data from the ; new section .muso10 CMP #$F6 ; If A is not $F6, jump to muso12 to check for the next BNE muso12 ; command ; If we get here then the command in A is $F6 ; ; <$F6 $xx> sets the volume envelope number to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE muso11 ; in the note data INC soundAddr+1 .muso11 STA volumeEnvelopeSQ1 ; Set volumeEnvelopeSQ1 to the volume envelope number ; that we just fetched JMP muso2 ; Jump back to muso2 to move on to the next entry from ; the note data .muso12 CMP #$F7 ; If A is not $F7, jump to muso14 to check for the next BNE muso14 ; command ; If we get here then the command in A is $F7 ; ; <$F7 $xx> sets the pitch envelope number to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE muso13 ; in the note data INC soundAddr+1 .muso13 STA pitchEnvelopeSQ1 ; Set pitchEnvelopeSQ1 to the pitch envelope number that ; we just fetched STY pitchIndexSQ1 ; Set pitchIndexSQ1 = 0 to point to the start of the ; data for pitch envelope A JMP muso2 ; Jump back to muso2 to move on to the next entry from ; the note data .muso14 CMP #$FA ; If A is not $FA, jump to muso16 to check for the next BNE muso16 ; command ; If we get here then the command in A is $FA ; ; <$FA %ddlc0000> configures the SQ1 channel as follows: ; ; * %dd = duty pulse length ; ; * %l set = infinite play ; * %l clear = one-shot play ; ; * %c set = constant volume ; * %c clear = envelope volume LDA (soundAddr),Y ; Fetch the next entry in the note data into A STA dutyLoopEnvSQ1 ; Store the entry we just fetched in dutyLoopEnvSQ1, to ; configure SQ1 as follows: ; ; * Bits 6-7 = duty pulse length ; ; * Bit 5 set = infinite play ; * Bit 5 clear = one-shot play ; ; * Bit 4 set = constant volume ; * Bit 4 clear = envelope volume INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE muso15 ; in the note data INC soundAddr+1 .muso15 JMP muso2 ; Jump back to muso2 to move on to the next entry from ; the note data .muso16 CMP #$F8 ; If A is not $F8, jump to muso17 to check for the next BNE muso17 ; command ; If we get here then the command in A is $F8 ; ; <$F8> sets the volume of the SQ1 channel to zero LDA #%00110000 ; Set the volume of the SQ1 channel to zero as follows: STA sq1Volume ; ; * Bits 6-7 = duty pulse length is 3 ; * Bit 5 set = infinite play ; * Bit 4 set = constant volume ; * Bits 0-3 = volume is 0 JMP muso7 ; Jump to muso7 to return from the subroutine, so we ; continue on from the next entry from the note data in ; the next iteration .muso17 CMP #$F9 ; If A is not $F9, jump to muso18 to check for the next BNE muso18 ; command ; If we get here then the command in A is $F9 ; ; <$F9> enables the volume envelope for the SQ1 channel JMP muso6 ; Jump to muso6 to return from the subroutine after ; setting applyVolumeSQ1 to $FF, so we apply the volume ; envelope, and then continue on from the next entry ; from the note data in the next iteration .muso18 CMP #$FD ; If A is not $FD, jump to muso20 to check for the next BNE muso20 ; command ; If we get here then the command in A is $FD ; ; <$F4 $xx> sets the SQ1 sweep to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE muso19 ; in the note data INC soundAddr+1 .muso19 STA sq1Sweep ; Store the entry we just fetched in sq1Sweep, which ; gets sent to the APU via SQ1_SWEEP JMP muso2 ; Jump back to muso2 to move on to the next entry from ; the note data .muso20 CMP #$FB ; If A is not $FB, jump to muso22 to check for the next BNE muso22 ; command ; If we get here then the command in A is $FB ; ; <$FB $xx> sets the tuning for all channels to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE muso21 ; in the note data INC soundAddr+1 .muso21 STA tuningAll ; Store the entry we just fetched in tuningAll, which ; sets the tuning for the SQ1, SQ2 and TRI channels (so ; this value gets added to every note on those channels) JMP muso2 ; Jump back to muso2 to move on to the next entry from ; the note data .muso22 CMP #$FC ; If A is not $FC, jump to muso24 to check for the next BNE muso24 ; command ; If we get here then the command in A is $FC ; ; <$FC $xx> sets the tuning for the SQ1 channel to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE muso23 ; in the note data INC soundAddr+1 .muso23 STA tuningSQ1 ; Store the entry we just fetched in tuningSQ1, which ; sets the tuning for the SQ1 channel (so this value ; gets added to every note on those channels) JMP muso2 ; Jump back to muso2 to move on to the next entry from ; the note data .muso24 CMP #$F5 ; If A is not $F5, jump to muso25 to check for the next BNE muso25 ; command ; If we get here then the command in A is $F5 ; ; <$F5 $xx &yy> changes tune to the tune data at &yyxx ; ; It does this by setting sectionListSQ1(1 0) to &yyxx ; and soundAddr(1 0) to the address stored in &yyxx ; ; To see why this works, consider switching to tune 2, ; for which we would use this command: ; ; <$F5 LO(tune2Data_SQ1) LO(tune2Data_SQ1)> ; ; This sets: ; ; sectionListSQ1(1 0) = tune2Data_SQ1 ; ; so from now on we fetch the addresses for each section ; of the tune from the table at tune2Data_SQ1 ; ; It also sets soundAddr(1 0) to the address in the ; first two bytes of tune2Data_SQ1, to give: ; ; soundAddr(1 0) = tune2Data_SQ1_0 ; ; So from this point on, note data is fetched from the ; table at tune2Data_SQ1_0, which contains notes and ; commands for the first section of tune 2 LDA (soundAddr),Y ; Fetch the next entry in the note data into A TAX ; Set sectionListSQ1(1 0) = &yyxx STA sectionListSQ1 ; INY ; Also set soundAddr(1 0) to &yyxx and increment the LDA (soundAddr),Y ; index in Y to 1, both of which we use below STX soundAddr STA soundAddr+1 STA sectionListSQ1+1 LDA #2 ; Set nextSectionSQ1(1 0) = 2 STA nextSectionSQ1 ; DEY ; So the next section after we play the first section STY nextSectionSQ1+1 ; of the new tune will be the second section ; ; Also decrement the index in Y back to 0 LDA (soundAddr),Y ; Set soundAddr(1 0) to the address stored at &yyxx TAX INY LDA (soundAddr),Y STA soundAddr+1 STX soundAddr JMP muso2 ; Jump back to muso2 to move on to the next entry from ; the note data .muso25 CMP #$F4 ; If A is not $F4, jump to muso27 to check for the next BNE muso27 ; command ; If we get here then the command in A is $F4 ; ; <$F4 $xx> sets the playback speed to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A, which ; contains the new speed INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE muso26 ; in the note data INC soundAddr+1 .muso26 STA tuneSpeed ; Set tuneSpeed and tuneSpeedCopy to A, to change the STA tuneSpeedCopy ; speed of the current tune to the specified speed JMP muso2 ; Jump back to muso2 to move on to the next entry from ; the note data .muso27 CMP #$FE ; If A is not $FE, jump to muso28 to check for the next BNE muso28 ; command ; If we get here then the command in A is $FE ; ; <$FE> stops the music and disables sound STY playMusic ; Set playMusic = 0 to stop playing the current tune, so ; only a new call to ChooseMusic will start the music ; again PLA ; Pull the return address from the stack, so the RTS PLA ; instruction at the end of StopSounds actually returns ; from the subroutine that called MakeMusic, so we stop ; the music and return to the MakeSounds routine (which ; is the only routine that calls MakeMusic) JMP StopSoundsS ; Jump to StopSounds via StopSoundsS to stop the music ; and return to the MakeSounds routine .muso28 BEQ muso28 ; If we get here then bit 7 of A was set but the value ; didn't match any of the checks above, so this ; instruction does nothing and we fall through into ; ApplyEnvelopeSQ1, ignoring the data in A ; ; I'm not sure why the instruction here is an infinite ; loop, but luckily it isn't triggered as A is never ; zero at this point
Name: ApplyEnvelopeSQ1 [Show more] Type: Subroutine Category: Sound Summary: Apply volume and pitch changes to the SQ1 channel Deep dive: Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeMusic calls ApplyEnvelopeSQ1
.ApplyEnvelopeSQ1 LDA applyVolumeSQ1 ; If applyVolumeSQ1 = 0 then we do not apply the volume BEQ musv2 ; envelope, so jump to musv2 to move on to the pitch ; envelope LDX volumeEnvelopeSQ1 ; Set X to the number of the volume envelope to apply LDA volumeEnvelopeLo,X ; Set soundAddr(1 0) to the address of the data for STA soundAddr ; volume envelope X from the (i.e. volumeEnvelope0 for LDA volumeEnvelopeHi,X ; envelope 0, volumeEnvelope1 for envelope 1, and so on) STA soundAddr+1 LDY #0 ; Set volumeRepeatSQ1 to the first byte of envelope LDA (soundAddr),Y ; data, which contains the number of times to repeat STA volumeRepeatSQ1 ; each entry in the envelope LDY volumeIndexSQ1 ; Set A to the byte of envelope data at the index in LDA (soundAddr),Y ; volumeIndexSQ1, which we increment to move through the ; data one byte at a time BMI musv1 ; If bit 7 of A is set then we just fetched the last ; byte of envelope data, so jump to musv1 to skip the ; following DEC volumeCounterSQ1 ; Decrement the counter for this envelope byte BPL musv1 ; If the counter is still positive, then we haven't yet ; done all the repeats for this envelope byte, so jump ; to musv1 to skip the following ; Otherwise this is the last repeat for this byte of ; envelope data, so now we reset the counter and move ; on to the next byte LDX volumeRepeatSQ1 ; Reset the repeat counter for this envelope to the STX volumeCounterSQ1 ; first byte of envelope data that we fetched above, ; which contains the number of times to repeat each ; entry in the envelope INC volumeIndexSQ1 ; Increment the index into the volume envelope so we ; move on to the next byte of data in the next iteration .musv1 AND #%00001111 ; Extract the low nibble from the envelope data, which ; contains the volume level ORA dutyLoopEnvSQ1 ; Set the high nibble of A to dutyLoopEnvSQ1, which gets ; set via command byte $FA and which contains the duty, ; loop and NES envelope settings to send to the APU STA sq1Volume ; Set sq1Volume to the resulting volume byte so it gets ; sent to the APU via SQ1_VOL .musv2 ; We now move on to the pitch envelope LDX pitchEnvelopeSQ1 ; Set X to the number of the pitch envelope to apply LDA pitchEnvelopeLo,X ; Set soundAddr(1 0) to the address of the data for STA soundAddr ; pitch envelope X from the (i.e. pitchEnvelope0 for LDA pitchEnvelopeHi,X ; envelope 0, pitchEnvelope1 for envelope 1, and so on) STA soundAddr+1 LDY pitchIndexSQ1 ; Set A to the byte of envelope data at the index in LDA (soundAddr),Y ; pitchIndexSQ1, which we increment to move through the ; data one byte at a time CMP #$80 ; If A is not $80 then we just fetched a valid byte of BNE musv3 ; envelope data, so jump to musv3 to process it ; If we get here then we just fetched a $80 from the ; pitch envelope, which indicates the end of the list of ; envelope values, so we now loop around to the start of ; the list, so it keeps repeating LDY #0 ; Set pitchIndexSQ1 = 0 to point to the start of the STY pitchIndexSQ1 ; data for pitch envelope X LDA (soundAddr),Y ; Set A to the byte of envelope data at index 0, so we ; can fall through into musv3 to process it .musv3 INC pitchIndexSQ1 ; Increment the index into the pitch envelope so we ; move on to the next byte of data in the next iteration CLC ; Set sq1Lo = sq1LoCopy + A ADC sq1LoCopy ; STA sq1Lo ; So this alters the low byte of the pitch that we send ; to the APU via SQ1_LO, altering it by the amount in ; the byte of data we just fetched from the pitch ; envelope RTS ; Return from the subroutine
Name: MakeMusicOnSQ2 [Show more] Type: Subroutine Category: Sound Summary: Play the current music on the SQ2 channel Deep dive: Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeMusic calls MakeMusicOnSQ2
.MakeMusicOnSQ2 DEC pauseCountSQ2 ; Decrement the sound counter for SQ2 BEQ must1 ; If the counter has reached zero, jump to must1 to make ; music on the SQ2 channel RTS ; Otherwise return from the subroutine .must1 LDA sectionDataSQ2 ; Set soundAddr(1 0) = sectionDataSQ2(1 0) STA soundAddr ; LDA sectionDataSQ2+1 ; So soundAddr(1 0) points to the note data for this STA soundAddr+1 ; part of the tune LDA #0 ; Set sq2Sweep = 0 STA sq2Sweep STA applyVolumeSQ2 ; Set applyVolumeSQ2 = 0 so we don't apply the volume ; envelope by default (this gets changed if we process ; note data below, as opposed to a command) .must2 LDY #0 ; Set Y to the next entry from the note data LDA (soundAddr),Y TAY INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE must3 ; in the note data INC soundAddr+1 .must3 TYA ; Set A to the next entry that we just fetched from the ; note data BMI must8 ; If bit 7 of A is set then this is a command byte, so ; jump to must8 to process it CMP #$60 ; If the note data in A is less than $60, jump to must4 BCC must4 ADC #$A0 ; The note data in A is between $60 and $7F, so set the STA startPauseSQ2 ; following: ; ; startPauseSQ2 = A - $5F ; ; We know the C flag is set as we just passed through a ; BCC, so the ADC actually adds $A1, which is the same ; as subtracting $5F ; ; So this sets startPauseSQ2 to a value between 1 and ; 32, corresponding to note data values between $60 and ; $7F JMP must2 ; Jump back to must2 to move on to the next entry from ; the note data .must4 ; If we get here then the note data in A is less than ; $60, which denotes a sound to send to the APU, so we ; now convert the data to a frequency and send it to the ; APU to make a sound on channel SQ2 CLC ; Set Y = (A + tuningAll + tuningSQ2) * 2 ADC tuningAll CLC ADC tuningSQ2 ASL A TAY LDA noteFrequency,Y ; Set (sq2Hi sq2Lo) the frequency for note Y STA sq2LoCopy ; STA sq2Lo ; Also save a copy of the low byte in sq2LoCopy LDA noteFrequency+1,Y STA sq2Hi LDX effectOnSQ2 ; If effectOnSQ2 is non-zero then a sound effect is BNE must5 ; being made on channel SQ2, so jump to must5 to skip ; writing the music data to the APU (so sound effects ; take precedence over music) LDX sq2Sweep ; Send sq2Sweep to the APU via SQ2_SWEEP STX SQ2_SWEEP LDX sq2Lo ; Send (sq2Hi sq2Lo) to the APU via SQ2_HI and SQ2_LO STX SQ2_LO STA SQ2_HI .must5 LDA #1 ; Set volumeIndexSQ2 = 1 STA volumeIndexSQ2 LDA volumeRepeatSQ2 ; Set volumeCounterSQ2 = volumeRepeatSQ2 STA volumeCounterSQ2 .must6 LDA #$FF ; Set applyVolumeSQ2 = $FF so we apply the volume STA applyVolumeSQ2 ; envelope in the next iteration .must7 LDA soundAddr ; Set sectionDataSQ2(1 0) = soundAddr(1 0) STA sectionDataSQ2 ; LDA soundAddr+1 ; This updates the pointer to the note data for the STA sectionDataSQ2+1 ; channel, so the next time we can pick up where we left ; off LDA startPauseSQ2 ; Set pauseCountSQ2 = startPauseSQ2 STA pauseCountSQ2 ; ; So if startPauseSQ2 is non-zero (as set by note data ; the range $60 to $7F), the next startPauseSQ2 ; iterations of MakeMusicOnSQ2 will do nothing RTS ; Return from the subroutine .must8 ; If we get here then bit 7 of the note data in A is ; set, so this is a command byte LDY #0 ; Set Y = 0, so we can use it in various commands below CMP #$FF ; If A is not $FF, jump to must10 to check for the next BNE must10 ; command ; If we get here then the command in A is $FF ; ; <$FF> moves to the next section in the current tune LDA nextSectionSQ2 ; Set soundAddr(1 0) to the following: CLC ; ADC sectionListSQ2 ; sectionListSQ2(1 0) + nextSectionSQ2(1 0) STA soundAddr ; LDA nextSectionSQ2+1 ; So soundAddr(1 0) points to the address of the next ADC sectionListSQ2+1 ; section in the current tune STA soundAddr+1 ; ; So if we are playing tune 2 and nextSectionSQ2(1 0) ; points to the second section, then soundAddr(1 0) ; will now point to the second address in tune2Data_SQ2, ; which itself points to the note data for the second ; section at tune2Data_SQ2_1 LDA nextSectionSQ2 ; Set nextSectionSQ2(1 0) = nextSectionSQ2(1 0) + 2 ADC #2 ; STA nextSectionSQ2 ; So nextSectionSQ2(1 0) now points to the next section, TYA ; as each section consists of two bytes in the table at ADC nextSectionSQ2+1 ; sectionListSQ2(1 0) STA nextSectionSQ2+1 LDA (soundAddr),Y ; If the address at soundAddr(1 0) is non-zero then it INY ; contains a valid address to the section's note data, ORA (soundAddr),Y ; so jump to must9 to skip the following BNE must9 ; ; This also increments the index in Y to 1 ; If we get here then the command is trying to move to ; the next section, but that section contains value of ; $0000 in the tuneData table, so there is no next ; section and we have reached the end of the tune, so ; instead we jump back to the start of the tune LDA sectionListSQ2 ; Set soundAddr(1 0) = sectionListSQ2(1 0) STA soundAddr ; LDA sectionListSQ2+1 ; So we start again by pointing soundAddr(1 0) to the STA soundAddr+1 ; first entry in the section list for channel SQ2, which ; contains the address of the first section's note data LDA #2 ; Set nextSectionSQ2(1 0) = 2 STA nextSectionSQ2 ; LDA #0 ; So the next section after we play the first section STA nextSectionSQ2+1 ; will be the second section .must9 ; By this point, Y has been incremented to 1 LDA (soundAddr),Y ; Set soundAddr(1 0) to the address at soundAddr(1 0) TAX ; DEY ; As we pointed soundAddr(1 0) to the address of the LDA (soundAddr),Y ; new section above, this fetches the first address from STA soundAddr ; the new section's address list, which points to the STX soundAddr+1 ; new section's note data ; ; So soundAddr(1 0) now points to the note data for the ; new section, so we're ready to start processing notes ; and commands when we rejoin the must2 loop JMP must2 ; Jump back to must2 to start processing data from the ; new section .must10 CMP #$F6 ; If A is not $F6, jump to must12 to check for the next BNE must12 ; command ; If we get here then the command in A is $F6 ; ; <$F6 $xx> sets the volume envelope number to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE must11 ; in the note data INC soundAddr+1 .must11 STA volumeEnvelopeSQ2 ; Set volumeEnvelopeSQ2 to the volume envelope number ; that we just fetched JMP must2 ; Jump back to must2 to move on to the next entry from ; the note data .must12 CMP #$F7 ; If A is not $F7, jump to must14 to check for the next BNE must14 ; command ; If we get here then the command in A is $F7 ; ; <$F7 $xx> sets the pitch envelope number to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE must13 ; in the note data INC soundAddr+1 .must13 STA pitchEnvelopeSQ2 ; Set pitchEnvelopeSQ2 to the pitch envelope number that ; we just fetched STY pitchIndexSQ2 ; Set pitchIndexSQ2 = 0 to point to the start of the ; data for pitch envelope A JMP must2 ; Jump back to must2 to move on to the next entry from ; the note data .must14 CMP #$FA ; If A is not $FA, jump to must16 to check for the next BNE must16 ; command ; If we get here then the command in A is $FA ; ; <$FA %ddlc0000> configures the SQ2 channel as follows: ; ; * %dd = duty pulse length ; ; * %l set = infinite play ; * %l clear = one-shot play ; ; * %c set = constant volume ; * %c clear = envelope volume LDA (soundAddr),Y ; Fetch the next entry in the note data into A STA dutyLoopEnvSQ2 ; Store the entry we just fetched in dutyLoopEnvSQ2, to ; configure SQ2 as follows: ; ; * Bits 6-7 = duty pulse length ; ; * Bit 5 set = infinite play ; * Bit 5 clear = one-shot play ; ; * Bit 4 set = constant volume ; * Bit 4 clear = envelope volume INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE must15 ; in the note data INC soundAddr+1 .must15 JMP must2 ; Jump back to must2 to move on to the next entry from ; the note data .must16 CMP #$F8 ; If A is not $F8, jump to must17 to check for the next BNE must17 ; command ; If we get here then the command in A is $F8 ; ; <$F8> sets the volume of the SQ2 channel to zero LDA #%00110000 ; Set the volume of the SQ2 channel to zero as follows: STA sq2Volume ; ; * Bits 6-7 = duty pulse length is 3 ; * Bit 5 set = infinite play ; * Bit 4 set = constant volume ; * Bits 0-3 = volume is 0 JMP must7 ; Jump to must7 to return from the subroutine, so we ; continue on from the next entry from the note data in ; the next iteration .must17 CMP #$F9 ; If A is not $F9, jump to must18 to check for the next BNE must18 ; command ; If we get here then the command in A is $F9 ; ; <$F9> enables the volume envelope for the SQ2 channel JMP must6 ; Jump to must6 to return from the subroutine after ; setting applyVolumeSQ2 to $FF, so we apply the volume ; envelope, and then continue on from the next entry ; from the note data in the next iteration .must18 CMP #$FD ; If A is not $FD, jump to must20 to check for the next BNE must20 ; command ; If we get here then the command in A is $FD ; ; <$F4 $xx> sets the SQ2 sweep to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE must19 ; in the note data INC soundAddr+1 .must19 STA sq2Sweep ; Store the entry we just fetched in sq2Sweep, which ; gets sent to the APU via SQ2_SWEEP JMP must2 ; Jump back to must2 to move on to the next entry from ; the note data .must20 CMP #$FB ; If A is not $FB, jump to must22 to check for the next BNE must22 ; command ; If we get here then the command in A is $FB ; ; <$FB $xx> sets the tuning for all channels to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE must21 ; in the note data INC soundAddr+1 .must21 STA tuningAll ; Store the entry we just fetched in tuningAll, which ; sets the tuning for the SQ2, SQ2 and TRI channels (so ; this value gets added to every note on those channels) JMP must2 ; Jump back to must2 to move on to the next entry from ; the note data .must22 CMP #$FC ; If A is not $FC, jump to must24 to check for the next BNE must24 ; command ; If we get here then the command in A is $FC ; ; <$FC $xx> sets the tuning for the SQ2 channel to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE must23 ; in the note data INC soundAddr+1 .must23 STA tuningSQ2 ; Store the entry we just fetched in tuningSQ2, which ; sets the tuning for the SQ2 channel (so this value ; gets added to every note on those channels) JMP must2 ; Jump back to must2 to move on to the next entry from ; the note data .must24 CMP #$F5 ; If A is not $F5, jump to must25 to check for the next BNE must25 ; command ; If we get here then the command in A is $F5 ; ; <$F5 $xx &yy> changes tune to the tune data at &yyxx ; ; It does this by setting sectionListSQ2(1 0) to &yyxx ; and soundAddr(1 0) to the address stored in &yyxx ; ; To see why this works, consider switching to tune 2, ; for which we would use this command: ; ; <$F5 LO(tune2Data_SQ2) HI(tune2Data_SQ2)> ; ; This sets: ; ; sectionListSQ2(1 0) = tune2Data_SQ2 ; ; so from now on we fetch the addresses for each section ; of the tune from the table at tune2Data_SQ2 ; ; It also sets soundAddr(1 0) to the address in the ; first two bytes of tune2Data_SQ2, to give: ; ; soundAddr(1 0) = tune2Data_SQ2_0 ; ; So from this point on, note data is fetched from the ; table at tune2Data_SQ2_0, which contains notes and ; commands for the first section of tune 2 LDA (soundAddr),Y ; Fetch the next entry in the note data into A TAX ; Set sectionListSQ2(1 0) = &yyxx STA sectionListSQ2 ; INY ; Also set soundAddr(1 0) to &yyxx and increment the LDA (soundAddr),Y ; index in Y to 1, both of which we use below STX soundAddr STA soundAddr+1 STA sectionListSQ2+1 LDA #2 ; Set nextSectionSQ2(1 0) = 2 STA nextSectionSQ2 ; DEY ; So the next section after we play the first section STY nextSectionSQ2+1 ; of the new tune will be the second section ; ; Also decrement the index in Y back to 0 LDA (soundAddr),Y ; Set soundAddr(1 0) to the address stored at &yyxx TAX INY LDA (soundAddr),Y STA soundAddr+1 STX soundAddr JMP must2 ; Jump back to must2 to move on to the next entry from ; the note data .must25 CMP #$F4 ; If A is not $F4, jump to must27 to check for the next BNE must27 ; command ; If we get here then the command in A is $F4 ; ; <$F4 $xx> sets the playback speed to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A, which ; contains the new speed INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE must26 ; in the note data INC soundAddr+1 .must26 STA tuneSpeed ; Set tuneSpeed and tuneSpeedCopy to A, to change the STA tuneSpeedCopy ; speed of the current tune to the specified speed JMP must2 ; Jump back to must2 to move on to the next entry from ; the note data .must27 CMP #$FE ; If A is not $FE, jump to must28 to check for the next BNE must28 ; command ; If we get here then the command in A is $FE ; ; <$FE> stops the music and disables sound STY playMusic ; Set playMusic = 0 to stop playing the current tune, so ; only a new call to ChooseMusic will start the music ; again PLA ; Pull the return address from the stack, so the RTS PLA ; instruction at the end of StopSounds actually returns ; from the subroutine that called MakeMusic, so we stop ; the music and return to the MakeSounds routine (which ; is the only routine that calls MakeMusic) JMP StopSoundsS ; Jump to StopSounds via StopSoundsS to stop the music ; and return to the MakeSounds routine .must28 BEQ must28 ; If we get here then bit 7 of A was set but the value ; didn't match any of the checks above, so this ; instruction does nothing and we fall through into ; ApplyEnvelopeSQ2, ignoring the data in A ; ; I'm not sure why the instruction here is an infinite ; loop, but luckily it isn't triggered as A is never ; zero at this point
Name: ApplyEnvelopeSQ2 [Show more] Type: Subroutine Category: Sound Summary: Apply volume and pitch changes to the SQ2 channel Deep dive: Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeMusic calls ApplyEnvelopeSQ2
.ApplyEnvelopeSQ2 LDA applyVolumeSQ2 ; If applyVolumeSQ2 = 0 then we do not apply the volume BEQ muss2 ; envelope, so jump to muss2 to move on to the pitch ; envelope LDX volumeEnvelopeSQ2 ; Set X to the number of the volume envelope to apply LDA volumeEnvelopeLo,X ; Set soundAddr(1 0) to the address of the data for STA soundAddr ; volume envelope X from the (i.e. volumeEnvelope0 for LDA volumeEnvelopeHi,X ; envelope 0, volumeEnvelope1 for envelope 1, and so on) STA soundAddr+1 LDY #0 ; Set volumeRepeatSQ2 to the first byte of envelope LDA (soundAddr),Y ; data, which contains the number of times to repeat STA volumeRepeatSQ2 ; each entry in the envelope LDY volumeIndexSQ2 ; Set A to the byte of envelope data at the index in LDA (soundAddr),Y ; volumeIndexSQ2, which we increment to move through the ; data one byte at a time BMI muss1 ; If bit 7 of A is set then we just fetched the last ; byte of envelope data, so jump to muss1 to skip the ; following DEC volumeCounterSQ2 ; Decrement the counter for this envelope byte BPL muss1 ; If the counter is still positive, then we haven't yet ; done all the repeats for this envelope byte, so jump ; to muss1 to skip the following ; Otherwise this is the last repeat for this byte of ; envelope data, so now we reset the counter and move ; on to the next byte LDX volumeRepeatSQ2 ; Reset the repeat counter for this envelope to the STX volumeCounterSQ2 ; first byte of envelope data that we fetched above, ; which contains the number of times to repeat each ; entry in the envelope INC volumeIndexSQ2 ; Increment the index into the volume envelope so we ; move on to the next byte of data in the next iteration .muss1 AND #%00001111 ; Extract the low nibble from the envelope data, which ; contains the volume level ORA dutyLoopEnvSQ2 ; Set the high nibble of A to dutyLoopEnvSQ2, which gets ; set via command byte $FA and which contains the duty, ; loop and NES envelope settings to send to the APU STA sq2Volume ; Set sq2Volume to the resulting volume byte so it gets ; sent to the APU via SQ2_VOL .muss2 ; We now move on to the pitch envelope LDX pitchEnvelopeSQ2 ; Set X to the number of the pitch envelope to apply LDA pitchEnvelopeLo,X ; Set soundAddr(1 0) to the address of the data for STA soundAddr ; pitch envelope X from the (i.e. pitchEnvelope0 for LDA pitchEnvelopeHi,X ; envelope 0, pitchEnvelope1 for envelope 1, and so on) STA soundAddr+1 LDY pitchIndexSQ2 ; Set A to the byte of envelope data at the index in LDA (soundAddr),Y ; pitchIndexSQ2, which we increment to move through the ; data one byte at a time CMP #$80 ; If A is not $80 then we just fetched a valid byte of BNE muss3 ; envelope data, so jump to muss3 to process it ; If we get here then we just fetched a $80 from the ; pitch envelope, which indicates the end of the list of ; envelope values, so we now loop around to the start of ; the list, so it keeps repeating LDY #0 ; Set pitchIndexSQ2 = 0 to point to the start of the STY pitchIndexSQ2 ; data for pitch envelope X LDA (soundAddr),Y ; Set A to the byte of envelope data at index 0, so we ; can fall through into muss3 to process it .muss3 INC pitchIndexSQ2 ; Increment the index into the pitch envelope so we ; move on to the next byte of data in the next iteration CLC ; Set sq2Lo = sq2LoCopy + A ADC sq2LoCopy ; STA sq2Lo ; So this alters the low byte of the pitch that we send ; to the APU via SQ2_LO, altering it by the amount in ; the byte of data we just fetched from the pitch ; envelope RTS ; Return from the subroutine
Name: MakeMusicOnTRI [Show more] Type: Subroutine Category: Sound Summary: Play the current music on the TRI channel Deep dive: Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeMusic calls MakeMusicOnTRI
.MakeMusicOnTRI DEC pauseCountTRI ; Decrement the sound counter for TRI BEQ musr1 ; If the counter has reached zero, jump to musr1 to make ; music on the TRI channel RTS ; Otherwise return from the subroutine .musr1 LDA sectionDataTRI ; Set soundAddr(1 0) = sectionDataTRI(1 0) STA soundAddr ; LDA sectionDataTRI+1 ; So soundAddr(1 0) points to the note data for this STA soundAddr+1 ; part of the tune .musr2 LDY #0 ; Set Y to the next entry from the note data LDA (soundAddr),Y TAY INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musr3 ; in the note data INC soundAddr+1 .musr3 TYA ; Set A to the next entry that we just fetched from the ; note data BMI musr6 ; If bit 7 of A is set then this is a command byte, so ; jump to musr6 to process it CMP #$60 ; If the note data in A is less than $60, jump to musr4 BCC musr4 ADC #$A0 ; The note data in A is between $60 and $7F, so set the STA startPauseTRI ; following: ; ; startPauseTRI = A - $5F ; ; We know the C flag is set as we just passed through a ; BCC, so the ADC actually adds $A1, which is the same ; as subtracting $5F ; ; So this sets startPauseTRI to a value between 1 and ; 32, corresponding to note data values between $60 and ; $7F JMP musr2 ; Jump back to musr2 to move on to the next entry from ; the note data .musr4 ; If we get here then the note data in A is less than ; $60, which denotes a sound to send to the APU, so we ; now convert the data to a frequency and send it to the ; APU to make a sound on channel TRI CLC ; Set Y = (A + tuningAll + tuningTRI) * 2 ADC tuningAll CLC ADC tuningTRI ASL A TAY LDA noteFrequency,Y ; Set (A triLo) the frequency for note Y STA triLoCopy ; STA triLo ; Also save a copy of the low byte in triLoCopy LDA noteFrequency+1,Y LDX triLo ; Send (A triLo) to the APU via TRI_HI and TRI_LO STX TRI_LO STA TRI_HI STA triHi ; Set (triHi triLo) = (A triLo), though this value is ; never read again, so this has no effect LDA volumeEnvelopeTRI ; Set the counter to the volume change to the value of STA volumeCounterTRI ; volumeEnvelopeTRI, which gets set by the $F6 command LDA #%10000001 ; Configure the TRI channel as follows: STA TRI_LINEAR ; ; * Bit 7 set = reload the linear counter ; ; * Bits 0-6 = counter reload value of 1 ; ; So this enables a cycling triangle wave on the TRI ; channel (so the channel is enabled) .musr5 LDA soundAddr ; Set sectionDataTRI(1 0) = soundAddr(1 0) STA sectionDataTRI ; LDA soundAddr+1 ; This updates the pointer to the note data for the STA sectionDataTRI+1 ; channel, so the next time we can pick up where we left ; off LDA startPauseTRI ; Set pauseCountTRI = startPauseTRI STA pauseCountTRI ; ; So if startPauseTRI is non-zero (as set by note data ; the range $60 to $7F), the next startPauseTRI ; iterations of MakeMusicOnTRI will do nothing RTS ; Return from the subroutine .musr6 ; If we get here then bit 7 of the note data in A is ; set, so this is a command byte LDY #0 ; Set Y = 0, so we can use it in various commands below CMP #$FF ; If A is not $FF, jump to musr8 to check for the next BNE musr8 ; command ; If we get here then the command in A is $FF ; ; <$FF> moves to the next section in the current tune LDA nextSectionTRI ; Set soundAddr(1 0) to the following: CLC ; ADC sectionListTRI ; sectionListTRI(1 0) + nextSectionTRI(1 0) STA soundAddr ; LDA nextSectionTRI+1 ; So soundAddr(1 0) points to the address of the next ADC sectionListTRI+1 ; section in the current tune STA soundAddr+1 ; ; So if we are playing tune 2 and nextSectionTRI(1 0) ; points to the second section, then soundAddr(1 0) ; will now point to the second address in tune2Data_TRI, ; which itself points to the note data for the second ; section at tune2Data_TRI_1 LDA nextSectionTRI ; Set nextSectionTRI(1 0) = nextSectionTRI(1 0) + 2 ADC #2 ; STA nextSectionTRI ; So nextSectionTRI(1 0) now points to the next section, TYA ; as each section consists of two bytes in the table at ADC nextSectionTRI+1 ; sectionListTRI(1 0) STA nextSectionTRI+1 LDA (soundAddr),Y ; If the address at soundAddr(1 0) is non-zero then it INY ; contains a valid address to the section's note data, ORA (soundAddr),Y ; so jump to musr7 to skip the following BNE musr7 ; ; This also increments the index in Y to 1 ; If we get here then the command is trying to move to ; the next section, but that section contains value of ; $0000 in the tuneData table, so there is no next ; section and we have reached the end of the tune, so ; instead we jump back to the start of the tune LDA sectionListTRI ; Set soundAddr(1 0) = sectionListTRI(1 0) STA soundAddr ; LDA sectionListTRI+1 ; So we start again by pointing soundAddr(1 0) to the STA soundAddr+1 ; first entry in the section list for channel TRI, which ; contains the address of the first section's note data LDA #2 ; Set nextSectionTRI(1 0) = 2 STA nextSectionTRI ; LDA #0 ; So the next section after we play the first section STA nextSectionTRI+1 ; will be the second section .musr7 ; By this point, Y has been incremented to 1 LDA (soundAddr),Y ; Set soundAddr(1 0) to the address at soundAddr(1 0) TAX ; DEY ; As we pointed soundAddr(1 0) to the address of the LDA (soundAddr),Y ; new section above, this fetches the first address from STA soundAddr ; the new section's address list, which points to the STX soundAddr+1 ; new section's note data ; ; So soundAddr(1 0) now points to the note data for the ; new section, so we're ready to start processing notes ; and commands when we rejoin the musr2 loop JMP musr2 ; Jump back to musr2 to start processing data from the ; new section .musr8 CMP #$F6 ; If A is not $F6, jump to musr10 to check for the next BNE musr10 ; command ; If we get here then the command in A is $F6 ; ; <$F6 $xx> sets the volume envelope counter to $xx ; ; In the other channels, this command lets us choose a ; volume envelope number ; ; In the case of the TRI channel, there isn't a volume ; envelope as such, because the channel is either off or ; on and doesn't have a volume setting, so instead of ; this command choosing a volume envelope number, it ; sets a counter that determines the number of ; iterations before the channel gets silenced ; ; I've kept the variable names in the same format as the ; other channels for consistency LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musr9 ; in the note data INC soundAddr+1 .musr9 STA volumeEnvelopeTRI ; Set volumeEnvelopeTRI to the volume envelope number ; that we just fetched JMP musr2 ; Jump back to musr2 to move on to the next entry from ; the note data .musr10 CMP #$F7 ; If A is not $F7, jump to musr12 to check for the next BNE musr12 ; command ; If we get here then the command in A is $F7 ; ; <$F7 $xx> sets the pitch envelope number to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musr11 ; in the note data INC soundAddr+1 .musr11 STA pitchEnvelopeTRI ; Set pitchEnvelopeTRI to the pitch envelope number that ; we just fetched STY pitchIndexTRI ; Set pitchIndexTRI = 0 to point to the start of the ; data for pitch envelope A JMP musr2 ; Jump back to musr2 to move on to the next entry from ; the note data .musr12 CMP #$F8 ; If A is not $F8, jump to musr13 to check for the next BNE musr13 ; command ; If we get here then the command in A is $F8 ; ; <$F8> sets the volume of the TRI channel to zero LDA #1 ; Set the counter in volumeCounterTRI to 1, so when we STA volumeCounterTRI ; return from the subroutine and call ApplyEnvelopeTRI, ; the TRI channel gets silenced JMP musr5 ; Jump to musr5 to return from the subroutine, so we ; continue on from the next entry from the note data in ; the next iteration .musr13 CMP #$F9 ; If A is not $F9, jump to musr14 to check for the next BNE musr14 ; command ; If we get here then the command in A is $F9 ; ; <$F9> enables the volume envelope for the TRI channel JMP musr5 ; Jump to musr5 to return from the subroutine, so we ; continue on from the next entry from the note data in ; the next iteration .musr14 CMP #$FB ; If A is not $FB, jump to musr16 to check for the next BNE musr16 ; command ; If we get here then the command in A is $FB ; ; <$FB $xx> sets the tuning for all channels to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musr15 ; in the note data INC soundAddr+1 .musr15 STA tuningAll ; Store the entry we just fetched in tuningAll, which ; sets the tuning for the TRI, TRI and TRI channels (so ; this value gets added to every note on those channels) JMP musr2 ; Jump back to musr2 to move on to the next entry from ; the note data .musr16 CMP #$FC ; If A is not $FC, jump to musr18 to check for the next BNE musr18 ; command ; If we get here then the command in A is $FC ; ; <$FC $xx> sets the tuning for the TRI channel to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musr17 ; in the note data INC soundAddr+1 .musr17 STA tuningTRI ; Store the entry we just fetched in tuningTRI, which ; sets the tuning for the TRI channel (so this value ; gets added to every note on those channels) JMP musr2 ; Jump back to musr2 to move on to the next entry from ; the note data .musr18 CMP #$F5 ; If A is not $F5, jump to musr19 to check for the next BNE musr19 ; command ; If we get here then the command in A is $F5 ; ; <$F5 $xx &yy> changes tune to the tune data at &yyxx ; ; It does this by setting sectionListTRI(1 0) to &yyxx ; and soundAddr(1 0) to the address stored in &yyxx ; ; To see why this works, consider switching to tune 2, ; for which we would use this command: ; ; <$F5 LO(tune2Data_TRI) LO(tune2Data_TRI)> ; ; This sets: ; ; sectionListTRI(1 0) = tune2Data_TRI ; ; so from now on we fetch the addresses for each section ; of the tune from the table at tune2Data_TRI ; ; It also sets soundAddr(1 0) to the address in the ; first two bytes of tune2Data_TRI, to give: ; ; soundAddr(1 0) = tune2Data_TRI_0 ; ; So from this point on, note data is fetched from the ; table at tune2Data_TRI_0, which contains notes and ; commands for the first section of tune 2 LDA (soundAddr),Y ; Fetch the next entry in the note data into A TAX ; Set sectionListTRI(1 0) = &yyxx STA sectionListTRI ; INY ; Also set soundAddr(1 0) to &yyxx and increment the LDA (soundAddr),Y ; index in Y to 1, both of which we use below STX soundAddr STA soundAddr+1 STA sectionListTRI+1 LDA #2 ; Set nextSectionTRI(1 0) = 2 STA nextSectionTRI ; DEY ; So the next section after we play the first section STY nextSectionTRI+1 ; of the new tune will be the second section ; ; Also decrement the index in Y back to 0 LDA (soundAddr),Y ; Set soundAddr(1 0) to the address stored at &yyxx TAX INY LDA (soundAddr),Y STA soundAddr+1 STX soundAddr JMP musr2 ; Jump back to musr2 to move on to the next entry from ; the note data .musr19 CMP #$F4 ; If A is not $F4, jump to musr21 to check for the next BNE musr21 ; command ; If we get here then the command in A is $F4 ; ; <$F4 $xx> sets the playback speed to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A, which ; contains the new speed INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musr20 ; in the note data INC soundAddr+1 .musr20 STA tuneSpeed ; Set tuneSpeed and tuneSpeedCopy to A, to change the STA tuneSpeedCopy ; speed of the current tune to the specified speed JMP musr2 ; Jump back to musr2 to move on to the next entry from ; the note data .musr21 CMP #$FE ; If A is not $FE, jump to musr22 to check for the next BNE musr22 ; command ; If we get here then the command in A is $FE ; ; <$FE> stops the music and disables sound STY playMusic ; Set playMusic = 0 to stop playing the current tune, so ; only a new call to ChooseMusic will start the music ; again PLA ; Pull the return address from the stack, so the RTS PLA ; instruction at the end of StopSounds actually returns ; from the subroutine that called MakeMusic, so we stop ; the music and return to the MakeSounds routine (which ; is the only routine that calls MakeMusic) JMP StopSoundsS ; Jump to StopSounds via StopSoundsS to stop the music ; and return to the MakeSounds routine .musr22 BEQ musr22 ; If we get here then bit 7 of A was set but the value ; didn't match any of the checks above, so this ; instruction does nothing and we fall through into ; ApplyEnvelopeTRI, ignoring the data in A ; ; I'm not sure why the instruction here is an infinite ; loop, but luckily it isn't triggered as A is never ; zero at this point
Name: ApplyEnvelopeTRI [Show more] Type: Subroutine Category: Sound Summary: Apply volume and pitch changes to the TRI channel Deep dive: Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeMusic calls ApplyEnvelopeTRI
.ApplyEnvelopeTRI LDA volumeCounterTRI ; If volumeCounterTRI = 0 then we are not counting down BEQ muse1 ; to a volume change, so jump to muse1 to move on to the ; pitch envelope DEC volumeCounterTRI ; Decrement the counter for the volume change BNE muse1 ; If the counter is still non-zero, then we haven't yet ; done counted down to the volume change, so jump to ; muse1 to skip the following LDA #%00000000 ; Configure the TRI channel as follows: STA TRI_LINEAR ; ; * Bit 7 clear = do not reload the linear counter ; ; * Bits 0-6 = counter reload value of 0 ; ; So this silences the TRI channel .muse1 ; We now move on to the pitch envelope LDX pitchEnvelopeTRI ; Set X to the number of the pitch envelope to apply LDA pitchEnvelopeLo,X ; Set soundAddr(1 0) to the address of the data for STA soundAddr ; pitch envelope X from the (i.e. pitchEnvelope0 for LDA pitchEnvelopeHi,X ; envelope 0, pitchEnvelope1 for envelope 1, and so on) STA soundAddr+1 LDY pitchIndexTRI ; Set A to the byte of envelope data at the index in LDA (soundAddr),Y ; pitchIndexTRI, which we increment to move through the ; data one byte at a time CMP #$80 ; If A is not $80 then we just fetched a valid byte of BNE muse2 ; envelope data, so jump to muse2 to process it ; If we get here then we just fetched a $80 from the ; pitch envelope, which indicates the end of the list of ; envelope values, so we now loop around to the start of ; the list, so it keeps repeating LDY #0 ; Set pitchIndexTRI = 0 to point to the start of the STY pitchIndexTRI ; data for pitch envelope X LDA (soundAddr),Y ; Set A to the byte of envelope data at index 0, so we ; can fall through into muse2 to process it .muse2 INC pitchIndexTRI ; Increment the index into the pitch envelope so we ; move on to the next byte of data in the next iteration CLC ; Set triLo = triLoCopy + A ADC triLoCopy ; STA triLo ; So this alters the low byte of the pitch that we send ; to the APU via TRI_LO, altering it by the amount in ; the byte of data we just fetched from the pitch ; envelope RTS ; Return from the subroutine
Name: MakeMusicOnNOISE [Show more] Type: Subroutine Category: Sound Summary: Play the current music on the NOISE channel Deep dive: Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeMusic calls MakeMusicOnNOISE
.MakeMusicOnNOISE DEC pauseCountNOISE ; Decrement the sound counter for NOISE BEQ musf1 ; If the counter has reached zero, jump to musf1 to make ; music on the NOISE channel RTS ; Otherwise return from the subroutine .musf1 LDA sectionDataNOISE ; Set soundAddr(1 0) = sectionDataNOISE(1 0) STA soundAddr ; LDA sectionDataNOISE+1 ; So soundAddr(1 0) points to the note data for this STA soundAddr+1 ; part of the tune STA applyVolumeNOISE ; Set applyVolumeNOISE = 0 so we don't apply the volume ; envelope by default (this gets changed if we process ; note data below, as opposed to a command) ; ; I'm not entirely sure why A is zero here - in fact, ; it's very unlikely to be zero - so it's possible that ; there is an LDA #0 instruction missing here and that ; this is a bug that applies the volume envelope of the ; NOISE channel too early .musf2 LDY #0 ; Set Y to the next entry from the note data LDA (soundAddr),Y TAY INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musf3 ; in the note data INC soundAddr+1 .musf3 TYA ; Set A to the next entry that we just fetched from the ; note data BMI musf7 ; If bit 7 of A is set then this is a command byte, so ; jump to musf7 to process it CMP #$60 ; If the note data in A is less than $60, jump to musf4 BCC musf4 ADC #$A0 ; The note data in A is between $60 and $7F, so set the STA startPauseNOISE ; following: ; ; startPauseNOISE = A - $5F ; ; We know the C flag is set as we just passed through a ; BCC, so the ADC actually adds $A1, which is the same ; as subtracting $5F ; ; So this sets startPauseNOISE to a value between 1 and ; 32, corresponding to note data values between $60 and ; $7F JMP musf2 ; Jump back to musf2 to move on to the next entry from ; the note data .musf4 ; If we get here then the note data in A is less than ; $60, which denotes a sound to send to the APU, so we ; now convert the data to a noise frequency (which we do ; by simply taking the low nibble of the note data, as ; this is just noise that doesn't need a conversion ; from note to frequency like the other channels) and ; send it to the APU to make a sound on channel NOISE AND #$0F ; Set (Y A) to the frequency for noise note Y STA noiseLoCopy ; STA noiseLo ; Also save a copy of the low byte in noiseLoCopy LDY #0 LDX effectOnNOISE ; If effectOnNOISE is non-zero then a sound effect is BNE musf5 ; being made on channel NOISE, so jump to musf5 to skip ; writing the music data to the APU (so sound effects ; take precedence over music) STA NOISE_LO ; Send (Y A) to the APU via NOISE_HI and NOISE_LO STY NOISE_HI .musf5 LDA #1 ; Set volumeIndexNOISE = 1 STA volumeIndexNOISE LDA volumeRepeatNOISE ; Set volumeCounterNOISE = volumeRepeatNOISE STA volumeCounterNOISE .musf6 LDA #$FF ; Set applyVolumeNOISE = $FF so we apply the volume STA applyVolumeNOISE ; envelope in the next iteration LDA soundAddr ; Set sectionDataNOISE(1 0) = soundAddr(1 0) STA sectionDataNOISE ; LDA soundAddr+1 ; This updates the pointer to the note data for the STA sectionDataNOISE+1 ; channel, so the next time we can pick up where we left ; off LDA startPauseNOISE ; Set pauseCountNOISE = startPauseNOISE STA pauseCountNOISE ; ; So if startPauseNOISE is non-zero (as set by note data ; the range $60 to $7F), the next startPauseNOISE ; iterations of MakeMusicOnNOISE will do nothing RTS ; Return from the subroutine .musf7 ; If we get here then bit 7 of the note data in A is ; set, so this is a command byte LDY #0 ; Set Y = 0, so we can use it in various commands below CMP #$FF ; If A is not $FF, jump to musf9 to check for the next BNE musf9 ; command ; If we get here then the command in A is $FF ; ; <$FF> moves to the next section in the current tune LDA nextSectionNOISE ; Set soundAddr(1 0) to the following: CLC ; ADC sectionListNOISE ; sectionListNOISE(1 0) + nextSectionNOISE(1 0) STA soundAddr ; LDA nextSectionNOISE+1 ; So soundAddr(1 0) points to the address of the next ADC sectionListNOISE+1 ; section in the current tune STA soundAddr+1 ; ; So if we are playing tune 2 and nextSectionNOISE(1 0) ; points to the second section, then soundAddr(1 0) ; will now point to the second address in ; tune2Data_NOISE, which itself points to the note data ; for the second section at tune2Data_NOISE_1 LDA nextSectionNOISE ; Set nextSectionNOISE(1 0) = nextSectionNOISE(1 0) + 2 ADC #2 ; STA nextSectionNOISE ; So nextSectionNOISE(1 0) now points to the next TYA ; section, as each section consists of two bytes in the ADC nextSectionNOISE+1 ; table at sectionListNOISE(1 0) STA nextSectionNOISE+1 LDA (soundAddr),Y ; If the address at soundAddr(1 0) is non-zero then it INY ; contains a valid address to the section's note data, ORA (soundAddr),Y ; so jump to musf8 to skip the following BNE musf8 ; ; This also increments the index in Y to 1 ; If we get here then the command is trying to move to ; the next section, but that section contains value of ; $0000 in the tuneData table, so there is no next ; section and we have reached the end of the tune, so ; instead we jump back to the start of the tune LDA sectionListNOISE ; Set soundAddr(1 0) = sectionListNOISE(1 0) STA soundAddr ; LDA sectionListNOISE+1 ; So we start again by pointing soundAddr(1 0) to the STA soundAddr+1 ; first entry in the section list for channel NOISE, ; which contains the address of the first section's note ; data LDA #2 ; Set nextSectionNOISE(1 0) = 2 STA nextSectionNOISE ; LDA #0 ; So the next section after we play the first section STA nextSectionNOISE+1 ; will be the second section .musf8 ; By this point, Y has been incremented to 1 LDA (soundAddr),Y ; Set soundAddr(1 0) to the address at soundAddr(1 0) TAX ; DEY ; As we pointed soundAddr(1 0) to the address of the LDA (soundAddr),Y ; new section above, this fetches the first address from STA soundAddr ; the new section's address list, which points to the STX soundAddr+1 ; new section's note data ; ; So soundAddr(1 0) now points to the note data for the ; new section, so we're ready to start processing notes ; and commands when we rejoin the musf2 loop JMP musf2 ; Jump back to musf2 to start processing data from the ; new section .musf9 CMP #$F6 ; If A is not $F6, jump to musf11 to check for the next BNE musf11 ; command ; If we get here then the command in A is $F6 ; ; <$F6 $xx> sets the volume envelope number to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musf10 ; in the note data INC soundAddr+1 .musf10 STA volumeEnvelopeNOISE ; Set volumeEnvelopeNOISE to the volume envelope ; number that we just fetched JMP musf2 ; Jump back to musf2 to move on to the next entry from ; the note data .musf11 CMP #$F7 ; If A is not $F7, jump to musf13 to check for the next BNE musf13 ; command ; If we get here then the command in A is $F7 ; ; <$F7 $xx> sets the pitch envelope number to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musf12 ; in the note data INC soundAddr+1 .musf12 STA pitchEnvelopeNOISE ; Set pitchEnvelopeNOISE to the pitch envelope number ; that we just fetched STY pitchIndexNOISE ; Set pitchIndexNOISE = 0 to point to the start of the ; data for pitch envelope A JMP musf2 ; Jump back to musf2 to move on to the next entry from ; the note data .musf13 CMP #$F8 ; If A is not $F8, jump to musf14 to check for the next BNE musf14 ; command ; If we get here then the command in A is $F8 ; ; <$F8> sets the volume of the NOISE channel to zero LDA #%00110000 ; Set the volume of the NOISE channel to zero as STA noiseVolume ; follows: ; ; * Bits 6-7 = duty pulse length is 3 ; * Bit 5 set = infinite play ; * Bit 4 set = constant volume ; * Bits 0-3 = volume is 0 JMP musf6 ; Jump to musf6 to return from the subroutine after ; setting applyVolumeNOISE to $FF, so we apply the ; volume envelope, and then continue on from the next ; entry from the note data in the next iteration .musf14 CMP #$F9 ; If A is not $F9, jump to musf15 to check for the next BNE musf15 ; command ; If we get here then the command in A is $F9 ; ; <$F9> enables the volume envelope for the NOISE ; channel JMP musf6 ; Jump to musf6 to return from the subroutine after ; setting applyVolumeNOISE to $FF, so we apply the ; volume envelope, and then continue on from the next ; entry from the note data in the next iteration .musf15 CMP #$F5 ; If A is not $F5, jump to musf16 to check for the next BNE musf16 ; command ; If we get here then the command in A is $F5 ; ; <$F5 $xx &yy> changes tune to the tune data at &yyxx ; ; It does this by setting sectionListNOISE(1 0) to &yyxx ; and soundAddr(1 0) to the address stored in &yyxx ; ; To see why this works, consider switching to tune 2, ; for which we would use this command: ; ; <$F5 LO(tune2Data_NOISE) LO(tune2Data_NOISE)> ; ; This sets: ; ; sectionListNOISE(1 0) = tune2Data_NOISE ; ; so from now on we fetch the addresses for each section ; of the tune from the table at tune2Data_NOISE ; ; It also sets soundAddr(1 0) to the address in the ; first two bytes of tune2Data_NOISE, to give: ; ; soundAddr(1 0) = tune2Data_NOISE_0 ; ; So from this point on, note data is fetched from the ; table at tune2Data_NOISE_0, which contains notes and ; commands for the first section of tune 2 LDA (soundAddr),Y ; Fetch the next entry in the note data into A TAX ; Set sectionListNOISE(1 0) = &yyxx STA sectionListNOISE ; INY ; Also set soundAddr(1 0) to &yyxx and increment the LDA (soundAddr),Y ; index in Y to 1, both of which we use below STX soundAddr STA soundAddr+1 STA sectionListNOISE+1 LDA #2 ; Set nextSectionNOISE(1 0) = 2 STA nextSectionNOISE ; DEY ; So the next section after we play the first section STY nextSectionNOISE+1 ; of the new tune will be the second section ; ; Also decrement the index in Y back to 0 LDA (soundAddr),Y ; Set soundAddr(1 0) to the address stored at &yyxx TAX INY LDA (soundAddr),Y STA soundAddr+1 STX soundAddr JMP musf2 ; Jump back to musf2 to move on to the next entry from ; the note data .musf16 CMP #$F4 ; If A is not $F4, jump to musf18 to check for the next BNE musf18 ; command ; If we get here then the command in A is $F4 ; ; <$F4 $xx> sets the playback speed to $xx LDA (soundAddr),Y ; Fetch the next entry in the note data into A, which ; contains the new speed INC soundAddr ; Increment soundAddr(1 0) to point to the next entry BNE musf17 ; in the note data INC soundAddr+1 .musf17 STA tuneSpeed ; Set tuneSpeed and tuneSpeedCopy to A, to change the STA tuneSpeedCopy ; speed of the current tune to the specified speed JMP musf2 ; Jump back to musf2 to move on to the next entry from ; the note data .musf18 CMP #$FE ; If A is not $FE, jump to musf19 to check for the next BNE musf19 ; command ; If we get here then the command in A is $FE ; ; <$FE> stops the music and disables sound STY playMusic ; Set playMusic = 0 to stop playing the current tune, so ; only a new call to ChooseMusic will start the music ; again PLA ; Pull the return address from the stack, so the RTS PLA ; instruction at the end of StopSounds actually returns ; from the subroutine that called MakeMusic, so we stop ; the music and return to the MakeSounds routine (which ; is the only routine that calls MakeMusic) JMP StopSoundsS ; Jump to StopSounds via StopSoundsS to stop the music ; and return to the MakeSounds routine .musf19 BEQ musf19 ; If we get here then bit 7 of A was set but the value ; didn't match any of the checks above, so this ; instruction does nothing and we fall through into ; ApplyEnvelopeNOISE, ignoring the data in A ; ; I'm not sure why the instruction here is an infinite ; loop, but luckily it isn't triggered as A is never ; zero at this point
Name: ApplyEnvelopeNOISE [Show more] Type: Subroutine Category: Sound Summary: Apply volume and pitch changes to the NOISE channel Deep dive: Music in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeMusic calls ApplyEnvelopeNOISE
.ApplyEnvelopeNOISE LDA applyVolumeNOISE ; If applyVolumeNOISE = 0 then we do not apply the BEQ musg2 ; volume envelope, so jump to musg2 to move on to the ; pitch envelope LDX volumeEnvelopeNOISE ; Set X to the number of the volume envelope to ; apply LDA volumeEnvelopeLo,X ; Set soundAddr(1 0) to the address of the data for STA soundAddr ; volume envelope X from the (i.e. volumeEnvelope0 for LDA volumeEnvelopeHi,X ; envelope 0, volumeEnvelope1 for envelope 1, and so on) STA soundAddr+1 LDY #0 ; Set volumeRepeatNOISE to the first byte of envelope LDA (soundAddr),Y ; data, which contains the number of times to repeat STA volumeRepeatNOISE ; each entry in the envelope LDY volumeIndexNOISE ; Set A to the byte of envelope data at the index in LDA (soundAddr),Y ; volumeIndexNOISE, which we increment to move through ; the data one byte at a time BMI musg1 ; If bit 7 of A is set then we just fetched the last ; byte of envelope data, so jump to musg1 to skip the ; following DEC volumeCounterNOISE ; Decrement the counter for this envelope byte BPL musg1 ; If the counter is still positive, then we haven't yet ; done all the repeats for this envelope byte, so jump ; to musg1 to skip the following ; Otherwise this is the last repeat for this byte of ; envelope data, so now we reset the counter and move ; on to the next byte LDX volumeRepeatNOISE ; Reset the repeat counter for this envelope to the STX volumeCounterNOISE ; first byte of envelope data that we fetched above, ; which contains the number of times to repeat each ; entry in the envelope INC volumeIndexNOISE ; Increment the index into the volume envelope so we ; move on to the next byte of data in the next iteration .musg1 AND #%00001111 ; Extract the low nibble from the envelope data, which ; contains the volume level ORA #%00110000 ; Set bits 5 and 6 to configure the NOISE channel as ; follows: ; ; * Bit 5 set = infinite play ; ; * Bit 4 set = constant volume ; ; Bits 6 and 7 are not used in the NOISE_VOL register STA noiseVolume ; Set noiseVolume to the resulting volume byte so it ; gets sent to the APU via NOISE_VOL .musg2 ; We now move on to the pitch envelope LDX pitchEnvelopeNOISE ; Set X to the number of the pitch envelope to apply LDA pitchEnvelopeLo,X ; Set soundAddr(1 0) to the address of the data for STA soundAddr ; pitch envelope X from the (i.e. pitchEnvelope0 for LDA pitchEnvelopeHi,X ; envelope 0, pitchEnvelope1 for envelope 1, and so on) STA soundAddr+1 LDY pitchIndexNOISE ; Set A to the byte of envelope data at the index in LDA (soundAddr),Y ; pitchIndexNOISE, which we increment to move through ; the data one byte at a time CMP #$80 ; If A is not $80 then we just fetched a valid byte of BNE musg3 ; envelope data, so jump to musg3 to process it ; If we get here then we just fetched a $80 from the ; pitch envelope, which indicates the end of the list of ; envelope values, so we now loop around to the start of ; the list, so it keeps repeating LDY #0 ; Set pitchIndexNOISE = 0 to point to the start of the STY pitchIndexNOISE ; data for pitch envelope X LDA (soundAddr),Y ; Set A to the byte of envelope data at index 0, so we ; can fall through into musg3 to process it .musg3 INC pitchIndexNOISE ; Increment the index into the pitch envelope so we ; move on to the next byte of data in the next iteration CLC ; Set noiseLo = low nibble of noiseLoCopy + A ADC noiseLoCopy ; AND #%00001111 ; So this alters the low byte of the pitch that we send STA noiseLo ; to the APU via NOISE_LO, altering it by the amount in ; the byte of data we just fetched from the pitch ; envelope ; ; We extract the low nibble because the high nibble is ; ignored in NOISE_LO, except for bit 7, which we want ; to clear so the period of the random noise generation ; is normal and not shortened RTS ; Return from the subroutine
Name: noteFrequency [Show more] Type: Variable Category: Sound Summary: A table of note frequencies Deep dive: Music in NES Elite
Context: See this variable on its own page References: This variable is used as follows: * MakeMusicOnSQ1 uses noteFrequency * MakeMusicOnSQ2 uses noteFrequency * MakeMusicOnTRI uses noteFrequency
.noteFrequency EQUW $031A ; The frequency for C# in octave 2 EQUW $02EC ; The frequency for D in octave 2 EQUW $02C2 ; The frequency for D# in octave 2 EQUW $029A ; The frequency for E in octave 2 EQUW $0275 ; The frequency for F in octave 2 EQUW $0252 ; The frequency for F# in octave 2 EQUW $0230 ; The frequency for G in octave 2 EQUW $0211 ; The frequency for G# in octave 2 EQUW $03E7 ; The frequency for C in octave 1 EQUW $03AF ; The frequency for B in octave 1 EQUW $037A ; The frequency for A# in octave 1 EQUW $0348 ; The frequency for A in octave 1 EQUW $031A ; The frequency for C# in octave 2 EQUW $02EC ; The frequency for D in octave 2 EQUW $02C2 ; The frequency for D# in octave 2 EQUW $029A ; The frequency for E in octave 2 EQUW $0275 ; The frequency for F in octave 2 EQUW $0252 ; The frequency for F# in octave 2 EQUW $0230 ; The frequency for G in octave 2 EQUW $0211 ; The frequency for G# in octave 2 EQUW $01F3 ; The frequency for A in octave 2 EQUW $01D7 ; The frequency for A# in octave 2 EQUW $01BD ; The frequency for B in octave 2 EQUW $01A4 ; The frequency for C in octave 3 EQUW $018D ; The frequency for C# in octave 3 EQUW $0176 ; The frequency for D in octave 3 EQUW $0161 ; The frequency for D# in octave 3 EQUW $014D ; The frequency for E in octave 3 EQUW $013B ; The frequency for F in octave 3 EQUW $0129 ; The frequency for F# in octave 3 EQUW $0118 ; The frequency for G in octave 3 EQUW $0108 ; The frequency for G# in octave 3 EQUW $00F9 ; The frequency for A in octave 3 EQUW $00EB ; The frequency for A# in octave 3 EQUW $00DE ; The frequency for B in octave 3 EQUW $00D1 ; The frequency for C in octave 4 EQUW $00C5 ; The frequency for C# in octave 4 EQUW $00BB ; The frequency for D in octave 4 EQUW $00B0 ; The frequency for D# in octave 4 EQUW $00A6 ; The frequency for E in octave 4 EQUW $009D ; The frequency for F in octave 4 EQUW $0094 ; The frequency for F# in octave 4 EQUW $008B ; The frequency for G in octave 4 EQUW $0084 ; The frequency for G# in octave 4 EQUW $007C ; The frequency for A in octave 4 EQUW $0075 ; The frequency for A# in octave 4 EQUW $006F ; The frequency for B in octave 4 EQUW $0068 ; The frequency for C in octave 5 EQUW $0062 ; The frequency for C# in octave 5 EQUW $005D ; The frequency for D in octave 5 EQUW $0057 ; The frequency for D# in octave 5 EQUW $0052 ; The frequency for E in octave 5 EQUW $004E ; The frequency for F in octave 5 EQUW $0049 ; The frequency for F# in octave 5 EQUW $0045 ; The frequency for G in octave 5 EQUW $0041 ; The frequency for G# in octave 5 EQUW $003E ; The frequency for A in octave 5 EQUW $003A ; The frequency for A# in octave 5 EQUW $0037 ; The frequency for B in octave 5 EQUW $0034 ; The frequency for C in octave 6 EQUW $0031 ; The frequency for C# in octave 6 EQUW $002E ; The frequency for D in octave 6 EQUW $002B ; The frequency for D# in octave 6 EQUW $0029 ; The frequency for E in octave 6 EQUW $0026 ; The frequency for F in octave 6 EQUW $0024 ; The frequency for F# in octave 6 EQUW $0022 ; The frequency for G in octave 6 EQUW $0020 ; The frequency for G# in octave 6 EQUW $001E ; The frequency for A in octave 6 EQUW $001C ; The frequency for A# in octave 6 EQUW $001B ; The frequency for B in octave 6 EQUW $0019 ; The frequency for C in octave 7 EQUW $0018 ; The frequency for C# in octave 7 EQUW $0016 ; The frequency for D in octave 7 EQUW $0015 ; The frequency for D# in octave 7 EQUW $0014 ; The frequency for E in octave 7 EQUW $0013 ; The frequency for F in octave 7 EQUW $0012 ; The frequency for F# in octave 7 EQUW $0011 ; The frequency for G in octave 7
Name: StartEffectOnSQ1 [Show more] Type: Subroutine Category: Sound Summary: Make a sound effect on the SQ1 channel Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * StartEffect calls StartEffectOnSQ1 * StartEffectOnSQ1S calls StartEffectOnSQ1

Arguments: A The number of the sound effect to make
.StartEffectOnSQ1 ASL A ; Set Y = A * 2 TAY ; ; So we can use Y as an index into the soundData table, ; which contains addresses of two bytes each LDA #0 ; Set effectOnSQ1 = 0 to disable sound generation on the STA effectOnSQ1 ; SQ1 channel while we set up the sound effect (as a ; value of 0 denotes that a sound effect is not being ; made on this channel, so none of the sound generation ; routines will do anything) ; ; We enable sound generation below once we have finished ; setting up the sound effect LDA soundData,Y ; Set soundAddr(1 0) to the address for this sound STA soundAddr ; effect from the soundData table, so soundAddr(1 0) LDA soundData+1,Y ; points to soundData0 for the sound data for sound STA soundAddr+1 ; effect 0, or to soundData1 for the sound data for ; sound effect 1, and so on ; There are 14 bytes of sound data for each sound effect ; that we now copy to soundByteSQ1, so we can do things ; like update the counters and store the current pitch ; as we make the sound effect LDY #13 ; Set a byte counter in Y for copying all 14 bytes .mefz1 LDA (soundAddr),Y ; Copy the Y-th byte of sound data for this sound effect STA soundByteSQ1,Y ; to the Y-th byte of soundByteSQ1 DEY ; Decrement the loop counter BPL mefz1 ; Loop back until we have copied all 14 bytes SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA soundByteSQ1+11 ; Set soundVolCountSQ1 = soundByteSQ1+11 STA soundVolCountSQ1 ; ; This initialises the counter in soundVolCountSQ1 ; with the value of byte #11, so it can be used to ; control how often we apply the volume envelope to the ; sound effect on channel SQ1 LDA soundByteSQ1+13 ; Set soundPitchEnvSQ1 = soundByteSQ1+13 STA soundPitchEnvSQ1 ; ; This initialises the counter in soundPitchEnvSQ1 ; with the value of byte #13, so it can be used to ; control how often we apply the pitch envelope to the ; sound effect on channel SQ1 LDA soundByteSQ1+1 ; Set soundPitCountSQ1 = soundByteSQ1+1 STA soundPitCountSQ1 ; ; This initialises the counter in soundPitCountSQ1 ; with the value of byte #1, so it can be used to ; control how often we send pitch data to the APU for ; the sound effect on channel SQ1 LDA soundByteSQ1+10 ; Set Y = soundByteSQ1+10 * 2 ASL A ; TAY ; So we can use Y as an index into the soundVolume ; table to fetch byte #10, as the table contains ; addresses of two bytes each LDA soundVolume,Y ; Set soundVolumeSQ1(1 0) to the address of the volume STA soundVolumeSQ1 ; envelope for this sound effect, as specified in STA soundAddr ; byte #10 of the sound effect's data LDA soundVolume+1,Y ; STA soundVolumeSQ1+1 ; This also sets soundAddr(1 0) to the same address STA soundAddr+1 LDY #0 ; Set Y = 0 so we can use indirect addressing below (we ; do not change the value of Y, this is just so we can ; implement the non-existent LDA (soundAddr) instruction ; by using LDA (soundAddr),Y instead) STY soundVolIndexSQ1 ; Set soundVolIndexSQ1 = 0, so we start processing the ; volume envelope from the first byte LDA (soundAddr),Y ; Take the first byte from the volume envelope for this ORA soundByteSQ1+6 ; sound effect, OR it with the sound effect's byte #6, STA SQ1_VOL ; and send the result to the APU via SQ1_VOL ; ; Data bytes in the volume envelope data only use the ; low nibble (the high nibble is only used to mark the ; end of the data), and the sound effect's byte #6 only ; uses the high nibble, so this sets the low nibble of ; the APU byte to the volume level from the data, and ; the high nibble of the APU byte to the configuration ; in byte #6 (which sets the duty pulse, looping and ; constant flags for the volume) LDA #0 ; Send 0 to the APU via SQ1_SWEEP to disable the sweep STA SQ1_SWEEP ; unit and stop the pitch from changing LDA soundByteSQ1+2 ; Set (soundHiSQ1 soundLoSQ1) to the 16-bit value in STA soundLoSQ1 ; bytes #2 and #3 of the sound data, which at this point STA SQ1_LO ; contains the first pitch value to send to the APU via LDA soundByteSQ1+3 ; (SQ1_HI SQ1_LO) STA soundHiSQ1 ; STA SQ1_HI ; We will be using these bytes to store the pitch bytes ; to send to the APU as we keep making the sound effect, ; so this just kicks off the process with the initial ; pitch value INC effectOnSQ1 ; Increment effectOnSQ1 to 1 to denote that a sound ; effect is now being generated on the SQ1 channel, so ; successive calls to MakeSoundOnSQ1 will now make the ; sound effect RTS ; Return from the subroutine
Name: StartEffect [Show more] Type: Subroutine Category: Sound Summary: Start making a sound effect on the specified channel Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * StartEffect_b6 calls StartEffect

Arguments: A The number of the sound effect to make X The sound channel on which to make the sound effect: * 0 = SQ1 * 1 = SQ2 * 2 = NOISE
.StartEffect DEX ; Decrement the channel number in X, so we can check the ; value in the following tests BMI msef1 ; If X is now negative then the channel number must be ; 0, so jump to msef1 to make the sound effect on the ; SQ1 channel BEQ StartEffectOnSQ2 ; If X is now zero then the channel number must be 1, so ; jump to StartEffectOnSQ2 to start making the sound ; effect on the SQ2 channel, returning from the ; subroutine using a tail call JMP StartEffectOnNOISE ; Otherwise the channel number must be 2, so jump to ; StartEffectOnNOISE to make the sound effect on the ; NOISE channel, returning from the subroutine using a ; tail call .msef1 JMP StartEffectOnSQ1 ; Jump to StartEffectOnSQ1 to start making the sound ; effect on the SQ1 channel, returning from the ; subroutine using a tail call
Name: StartEffectOnSQ2 [Show more] Type: Subroutine Category: Sound Summary: Make a sound effect on the SQ2 channel Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * StartEffect calls StartEffectOnSQ2 * StartEffectOnSQ2S calls StartEffectOnSQ2

Arguments: A The number of the sound effect to make
.StartEffectOnSQ2 ASL A ; Set Y = A * 2 TAY ; ; So we can use Y as an index into the soundData table, ; which contains addresses of two bytes each LDA #0 ; Set effectOnSQ2 = 0 to disable sound generation on the STA effectOnSQ2 ; SQ2 channel while we set up the sound effect (as a ; value of 0 denotes that a sound effect is not being ; made on this channel, so none of the sound generation ; routines will do anything) ; ; We enable sound generation below once we have finished ; setting up the sound effect LDA soundData,Y ; Set soundAddr(1 0) to the address for this sound STA soundAddr ; effect from the soundData table, so soundAddr(1 0) LDA soundData+1,Y ; points to soundData0 for the sound data for sound STA soundAddr+1 ; effect 0, or to soundData1 for the sound data for ; sound effect 1, and so on ; There are 14 bytes of sound data for each sound effect ; that we now copy to soundByteSQ2, so we can do things ; like update the counters and store the current pitch ; as we make the sound effect LDY #13 ; Set a byte counter in Y for copying all 14 bytes .mefo1 LDA (soundAddr),Y ; Copy the Y-th byte of sound data for this sound effect STA soundByteSQ2,Y ; to the Y-th byte of soundByteSQ2 DEY ; Decrement the loop counter BPL mefo1 ; Loop back until we have copied all 14 bytes SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA soundByteSQ2+11 ; Set soundVolCountSQ2 = soundByteSQ2+11 STA soundVolCountSQ2 ; ; This initialises the counter in soundVolCountSQ2 ; with the value of byte #11, so it can be used to ; control how often we apply the volume envelope to the ; sound effect on channel SQ2 LDA soundByteSQ2+13 ; Set soundPitchEnvSQ2 = soundByteSQ2+13 STA soundPitchEnvSQ2 ; ; This initialises the counter in soundPitchEnvSQ2 ; with the value of byte #13, so it can be used to ; control how often we apply the pitch envelope to the ; sound effect on channel SQ2 LDA soundByteSQ2+1 ; Set soundPitCountSQ2 = soundByteSQ2+1 STA soundPitCountSQ2 ; ; This initialises the counter in soundPitCountSQ2 ; with the value of byte #1, so it can be used to ; control how often we send pitch data to the APU for ; the sound effect on channel SQ2 LDA soundByteSQ2+10 ; Set Y = soundByteSQ2+10 * 2 ASL A ; TAY ; So we can use Y as an index into the soundVolume ; table to fetch byte #10, as the table contains ; addresses of two bytes each LDA soundVolume,Y ; Set soundVolumeSQ2(1 0) to the address of the volume STA soundVolumeSQ2 ; envelope for this sound effect, as specified in STA soundAddr ; byte #10 of the sound effect's data LDA soundVolume+1,Y ; STA soundVolumeSQ2+1 ; This also sets soundAddr(1 0) to the same address STA soundAddr+1 LDY #0 ; Set Y = 0 so we can use indirect addressing below (we ; do not change the value of Y, this is just so we can ; implement the non-existent LDA (soundAddr) instruction ; by using LDA (soundAddr),Y instead) STY soundVolIndexSQ2 ; Set soundVolIndexSQ2 = 0, so we start processing the ; volume envelope from the first byte LDA (soundAddr),Y ; Take the first byte from the volume envelope for this ORA soundByteSQ2+6 ; sound effect, OR it with the sound effect's byte #6, STA SQ2_VOL ; and send the result to the APU via SQ2_VOL ; ; Data bytes in the volume envelope data only use the ; low nibble (the high nibble is only used to mark the ; end of the data), and the sound effect's byte #6 only ; uses the high nibble, so this sets the low nibble of ; the APU byte to the volume level from the data, and ; the high nibble of the APU byte to the configuration ; in byte #6 (which sets the duty pulse, looping and ; constant flags for the volume) LDA #0 ; Send 0 to the APU via SQ2_SWEEP to disable the sweep STA SQ2_SWEEP ; unit and stop the pitch from changing LDA soundByteSQ2+2 ; Set (soundHiSQ2 soundLoSQ2) to the 16-bit value in STA soundLoSQ2 ; bytes #2 and #3 of the sound data, which at this point STA SQ2_LO ; contains the first pitch value to send to the APU via LDA soundByteSQ2+3 ; (SQ2_HI SQ2_LO) STA soundHiSQ2 ; STA SQ2_HI ; We will be using these bytes to store the pitch bytes ; to send to the APU as we keep making the sound effect, ; so this just kicks off the process with the initial ; pitch value INC effectOnSQ2 ; Increment effectOnSQ2 to 1 to denote that a sound ; effect is now being generated on the SQ2 channel, so ; successive calls to MakeSoundOnSQ2 will now make the ; sound effect RTS ; Return from the subroutine
Name: StartEffectOnNOISE [Show more] Type: Subroutine Category: Sound Summary: Make a sound effect on the NOISE channel Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * StartEffect calls StartEffectOnNOISE * StartEffectOnNOISES calls StartEffectOnNOISE

Arguments: A The number of the sound effect to make
.StartEffectOnNOISE ASL A ; Set Y = A * 2 TAY ; ; So we can use Y as an index into the soundData table, ; which contains addresses of two bytes each LDA #0 ; Set effectOnNOISE = 0 to disable sound generation on STA effectOnNOISE ; the NOISE channel while we set up the sound effect (as ; a value of 0 denotes that a sound effect is not being ; made on this channel, so none of the sound generation ; routines will do anything) ; ; We enable sound generation below once we have finished ; setting up the sound effect LDA soundData,Y ; Set soundAddr(1 0) to the address for this sound STA soundAddr ; effect from the soundData table, so soundAddr(1 0) LDA soundData+1,Y ; points to soundData0 for the sound data for sound STA soundAddr+1 ; effect 0, or to soundData1 for the sound data for ; sound effect 1, and so on ; There are 14 bytes of sound data for each sound effect ; that we now copy to soundByteNOISE, so we can do ; things like update the counters and store the current ; pitch as we make the sound effect LDY #13 ; Set a byte counter in Y for copying all 14 bytes .meft1 LDA (soundAddr),Y ; Copy the Y-th byte of sound data for this sound effect STA soundByteNOISE,Y ; to the Y-th byte of soundByteNOISE DEY ; Decrement the loop counter BPL meft1 ; Loop back until we have copied all 14 bytes SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA soundByteNOISE+11 ; Set soundVolCountNOISE = soundByteNOISE+11 STA soundVolCountNOISE ; ; This initialises the counter in soundVolCountNOISE ; with the value of byte #11, so it can be used to ; control how often we apply the volume envelope to the ; sound effect on channel NOISE LDA soundByteNOISE+13 ; Set soundPitchEnvNOISE = soundByteNOISE+13 STA soundPitchEnvNOISE ; ; This initialises the counter in soundPitchEnvNOISE ; with the value of byte #13, so it can be used to ; control how often we apply the pitch envelope to the ; sound effect on channel NOISE LDA soundByteNOISE+1 ; Set soundPitCountNOISE = soundByteNOISE+1 STA soundPitCountNOISE ; ; This initialises the counter in soundPitCountNOISE ; with the value of byte #1, so it can be used to ; control how often we send pitch data to the APU for ; the sound effect on channel NOISE LDA soundByteNOISE+10 ; Set Y = soundByteNOISE+10 * 2 ASL A ; TAY ; So we can use Y as an index into the soundVolume ; table to fetch byte #10, as the table contains ; addresses of two bytes each LDA soundVolume,Y ; Set soundVolumeNOISE(1 0) to the address of the volume STA soundVolumeNOISE ; envelope for this sound effect, as specified in STA soundAddr ; byte #10 of the sound effect's data LDA soundVolume+1,Y ; STA soundVolumeNOISE+1 ; This also sets soundAddr(1 0) to the same address STA soundAddr+1 LDY #0 ; Set Y = 0 so we can use indirect addressing below (we ; do not change the value of Y, this is just so we can ; implement the non-existent LDA (soundAddr) instruction ; by using LDA (soundAddr),Y instead) STY soundVolIndexNOISE ; Set soundVolIndexNOISE = 0, so we start processing the ; volume envelope from the first byte LDA (soundAddr),Y ; Take the first byte from the volume envelope for this ORA soundByteNOISE+6 ; sound effect, OR it with the sound effect's byte #6, STA NOISE_VOL ; and send the result to the APU via NOISE_VOL ; ; Data bytes in the volume envelope data only use the ; low nibble (the high nibble is only used to mark the ; end of the data), and the sound effect's byte #6 only ; uses the high nibble, so this sets the low nibble of ; the APU byte to the volume level from the data, and ; the high nibble of the APU byte to the configuration ; in byte #6 (which sets the duty pulse, looping and ; constant flags for the volume) LDA #0 ; This instruction would send 0 to the APU via STA NOISE_VOL+1 ; NOISE_SWEEP to disable the sweep unit and stop the ; pitch from changing, but the NOISE channel doesn't ; have a sweep unit, so this has no effect and is ; presumably left over from the same code for the SQ1 ; and SQ2 channels LDA soundByteNOISE+2 ; Set (0 soundLoNOISE) to the 8-bit value in byte #2 of AND #$0F ; the sound data, which at this point contains the first STA soundLoNOISE ; pitch value to send to the APU via (NOISE_HI NOISE_LO) STA NOISE_LO ; LDA #0 ; We ignore byte #3 as the NOISE channel only has an STA NOISE_HI ; 8-bit pitch range ; ; We will be using soundLoNOISE to store the pitch byte ; to send to the APU as we keep making the sound effect, ; so this just kicks off the process with the initial ; pitch value INC effectOnNOISE ; Increment effectOnNOISE to 1 to denote that a sound ; effect is now being generated on the NOISE channel, so ; successive calls to MakeSoundOnNOISE will now make the ; sound effect RTS ; Return from the subroutine
Name: MakeSound [Show more] Type: Subroutine Category: Sound Summary: Make the current sound effects on the SQ1, SQ2 and NOISE channels Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeSounds calls MakeSound
.MakeSound JSR UpdateVibratoSeeds ; Update the sound seeds that are used to randomise the ; vibrato effect JSR MakeSoundOnSQ1 ; Make the current sound effect on the SQ1 channel JSR MakeSoundOnSQ2 ; Make the current sound effect on the SQ2 channel JMP MakeSoundOnNOISE ; Make the current sound effect on the NOISE channel, ; returning from the subroutine using a tail call
Name: MakeSoundOnSQ1 [Show more] Type: Subroutine Category: Sound Summary: Make the current sound effect on the SQ1 channel Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeSound calls MakeSoundOnSQ1
.MakeSoundOnSQ1 LDA effectOnSQ1 ; If effectOnSQ1 is non-zero then a sound effect is BNE mscz1 ; being made on channel SQ1, so jump to mscz1 to keep ; making it RTS ; Otherwise return from the subroutine .mscz1 LDA soundByteSQ1+0 ; If the remaining number of iterations for this sound BNE mscz3 ; effect in sound byte #0 is non-zero, jump to mscz3 to ; keep making the sound LDX soundByteSQ1+12 ; If byte #12 of the sound effect data is non-zero, then BNE mscz3 ; this sound effect keeps looping, so jump to mscz3 to ; keep making the sound LDA enableSound ; If enableSound = 0 then sound is disabled, so jump to BEQ mscz2 ; mscz2 to silence the SQ1 channel and return from the ; subroutine ; If we get here then we have finished making the sound ; effect, so we now send the volume and pitch values for ; the music to the APU, so if there is any music playing ; it will pick up again, and we mark this sound channel ; as clear of sound effects LDA sq1Volume ; Send sq1Volume to the APU via SQ1_VOL, which is the STA SQ1_VOL ; volume byte of any music that was playing when the ; sound effect took precedence LDA sq1Lo ; Send (sq1Hi sq1Lo) to the APU via (SQ1_HI SQ1_LO), STA SQ1_LO ; which is the pitch of any music that was playing when LDA sq1Hi ; the sound effect took precedence STA SQ1_HI STX effectOnSQ1 ; Set effectOnSQ1 = 0 to mark the SQL channel as clear ; of sound effects, so the channel can be used for music ; and is ready for the next sound effect RTS ; Return from the subroutine .mscz2 ; If we get here then sound is disabled, so we need to ; silence the SQ1 channel LDA #%00110000 ; Set the volume of the SQ1 channel to zero as follows: STA SQ1_VOL ; ; * Bits 6-7 = duty pulse length is 3 ; * Bit 5 set = infinite play ; * Bit 4 set = constant volume ; * Bits 0-3 = volume is 0 STX effectOnSQ1 ; Set effectOnSQ1 = 0 to mark the SQL channel as clear ; of sound effects, so the channel can be used for music ; and is ready for the next sound effect RTS ; Return from the subroutine .mscz3 ; If we get here then we need to keep making the sound ; effect on channel SQ1 DEC soundByteSQ1+0 ; Decrement the remaining length of the sound in byte #0 ; as we are about to make the sound for another ; iteration DEC soundVolCountSQ1 ; Decrement the volume envelope counter so we count down ; towards the point where we apply the volume envelope BNE mscz5 ; If the volume envelope counter has not reached zero ; then jump to mscz5, as we don't apply the next entry ; from the volume envelope yet ; If we get here then the counter in soundVolCountSQ1 ; just reached zero, so we apply the next entry from the ; volume envelope LDA soundByteSQ1+11 ; Reset the volume envelope counter to byte #11 from the STA soundVolCountSQ1 ; sound effect's data, which controls how often we apply ; the volume envelope to the sound effect LDY soundVolIndexSQ1 ; Set Y to the index of the current byte in the volume ; envelope LDA soundVolumeSQ1 ; Set soundAddr(1 0) = soundVolumeSQ1(1 0) STA soundAddr ; LDA soundVolumeSQ1+1 ; So soundAddr(1 0) contains the address of the volume STA soundAddr+1 ; envelope for this sound effect LDA (soundAddr),Y ; Set A to the data byte at the current index in the ; volume envelope BPL mscz4 ; If bit 7 is clear then we just fetched a volume value ; from the envelope, so jump to mscz4 to apply it ; If we get here then A must be $80 or $FF, as those are ; the only two valid entries in the volume envelope that ; have bit 7 set ; ; $80 means we loop back to the start of the envelope, ; while $FF means the envelope ends here CMP #$80 ; If A is not $80 then we must have just fetched $FF BNE mscz5 ; from the envelope, so jump to mscz5 to exit the ; envelope ; If we get here then we just fetched a $80 from the ; envelope data, so we now loop around to the start of ; the envelope, so it keeps repeating LDY #0 ; Set Y to zero so we fetch data from the start of the ; envelope again LDA (soundAddr),Y ; Set A to the byte of envelope data at index 0, so we ; can fall through into mscz4 to process it .mscz4 ; If we get here then A contains an entry from the ; volume envelope for this sound effect, so now we send ; it to the APU to change the volume ORA soundByteSQ1+6 ; OR the envelope byte with the sound effect's byte #6, STA SQ1_VOL ; and send the result to the APU via SQ1_VOL ; ; Data bytes in the volume envelope data only use the ; low nibble (the high nibble is only used to mark the ; end of the data), and the sound effect's byte #6 only ; uses the high nibble, so this sets the low nibble of ; the APU byte to the volume level from the data, and ; the high nibble of the APU byte to the configuration ; in byte #6 (which sets the duty pulse, looping and ; constant flags for the volume) INY ; Increment the index of the current byte in the volume STY soundVolIndexSQ1 ; envelope so on the next iteration we move on to the ; next byte in the envelope .mscz5 ; Now that we are done with the volume envelope, it's ; time to move on to the pitch of the sound effect LDA soundPitCountSQ1 ; If the byte #1 counter has not yet run down to zero, BNE mscz8 ; jump to mscz8 to skip the following, so we don't send ; pitch data to the APU on this iteration ; If we get here then the counter in soundPitCountSQ1 ; (which counts down from the value of byte #1) has run ; down to zero, so we now send pitch data to the ALU if ; if we haven't yet sent it all LDA soundByteSQ1+12 ; If byte #12 is non-zero then the sound effect loops BNE mscz6 ; infinitely, so jump to mscz6 to send pitch data to the ; APU LDA soundByteSQ1+9 ; Otherwise, if the counter in byte #9 has not run down BNE mscz6 ; then we haven't yet sent pitch data for enough ; iterations, so jump to mscz6 to send pitch data to the ; APU RTS ; Return from the subroutine .mscz6 ; If we get here then we are sending pitch data to the ; APU on this iteration, so now we do just that DEC soundByteSQ1+9 ; Decrement the counter in byte #9, which contains the ; number of iterations for which we send pitch data to ; the APU (as that's what we are doing) LDA soundByteSQ1+1 ; Reset the soundPitCountSQ1 counter to the value of STA soundPitCountSQ1 ; byte #1 so it can start counting down again to trigger ; the next pitch change after this one LDA soundByteSQ1+2 ; Set A to the low byte of the sound effect's current ; pitch, which is in byte #2 of the sound data LDX soundByteSQ1+7 ; If byte #7 is zero then vibrato is disabled, so jump BEQ mscz7 ; to mscz7 to skip the following instruction ADC soundVibrato ; Byte #7 is non-zero, so add soundVibrato to the pitch ; of the sound in A to apply vibrato (this also adds the ; C flag, which is not in a fixed state, so this adds an ; extra level of randomness to the vibrato effect) .mscz7 STA soundLoSQ1 ; Store the value of A (i.e. the low byte of the sound ; effect's pitch, possibly with added vibrato) in ; soundLoSQ1 STA SQ1_LO ; Send the value of soundLoSQ1 to the APU via SQ1_LO LDA soundByteSQ1+3 ; Set A to the high byte of the sound effect's current ; pitch, which is in byte #3 of the sound data STA soundHiSQ1 ; Store the value of A (i.e. the high byte of the sound ; effect's pitch) in soundHiSQ1 STA SQ1_HI ; Send the value of soundHiSQ1 to the APU via SQ1_HI .mscz8 DEC soundPitCountSQ1 ; Decrement the byte #1 counter, as we have now done one ; more iteration of the sound effect LDA soundByteSQ1+13 ; If byte #13 of the sound data is zero then we apply BEQ mscz9 ; pitch variation in every iteration (if enabled), so ; jump to mscz9 to skip the following and move straight ; to the pitch variation checks DEC soundPitchEnvSQ1 ; Otherwise decrement the byte #13 counter to count down ; towards the point where we apply pitch variation BNE mscz11 ; If the counter is not yet zero, jump to mscz11 to ; return from the subroutine without applying pitch ; variation, as the counter has not yet reached that ; point ; If we get here then the byte #13 counter just ran down ; to zero, so we need to apply pitch variation (if ; enabled) STA soundPitchEnvSQ1 ; Reset the soundPitchEnvSQ1 counter to the value of ; byte #13 so it can start counting down again, for the ; next pitch variation after this one .mscz9 LDA soundByteSQ1+8 ; Set A to byte #8 of the sound data, which determines ; whether pitch variation is enabled BEQ mscz11 ; If A is zero then pitch variation is not enabled, so ; jump to mscz11 to return from the subroutine without ; applying pitch variation ; If we get here then pitch variation is enabled, so now ; we need to apply it BMI mscz10 ; If A is negative then we need to add the value to the ; pitch's period, so jump to mscz10 ; If we get here then we need to subtract the 16-bit ; value in bytes #4 and #5 from the pitch's period in ; (soundHiSQ1 soundLoSQ1) ; ; Reducing the pitch's period increases its frequency, ; so this makes the note frequency higher LDA soundLoSQ1 ; Subtract the 16-bit value in bytes #4 and #5 of the SEC ; sound data from (soundHiSQ1 soundLoSQ1), updating SBC soundByteSQ1+4 ; (soundHiSQ1 soundLoSQ1) to the result, and sending STA soundLoSQ1 ; it to the APU via (SQ1_HI SQ1_LO) STA SQ1_LO ; LDA soundHiSQ1 ; Note that bits 2 to 7 of the high byte are cleared so SBC soundByteSQ1+5 ; the length counter does not reload AND #%00000011 STA soundHiSQ1 STA SQ1_HI RTS ; Return from the subroutine .mscz10 ; If we get here then we need to add the 16-bit value ; in bytes #4 and #5 to the pitch's period in ; (soundHiSQ1 soundLoSQ1) ; ; Increasing the pitch's period reduces its frequency, ; so this makes the note frequency lower LDA soundLoSQ1 ; Add the 16-bit value in bytes #4 and #5 of the sound CLC ; data to (soundHiSQ1 soundLoSQ1), updating ADC soundByteSQ1+4 ; (soundHiSQ1 soundLoSQ1) to the result, and sending STA soundLoSQ1 ; it to the APU via (SQ1_HI SQ1_LO) STA SQ1_LO ; LDA soundHiSQ1 ; Note that bits 2 to 7 of the high byte are cleared so ADC soundByteSQ1+5 ; the length counter does not reload AND #%00000011 STA soundHiSQ1 STA SQ1_HI .mscz11 RTS ; Return from the subroutine
Name: MakeSoundOnSQ2 [Show more] Type: Subroutine Category: Sound Summary: Make the current sound effect on the SQ2 channel Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeSound calls MakeSoundOnSQ2
.MakeSoundOnSQ2 LDA effectOnSQ2 ; If effectOnSQ2 is non-zero then a sound effect is BNE msco1 ; being made on channel SQ2, so jump to msco1 to keep ; making it RTS ; Otherwise return from the subroutine .msco1 LDA soundByteSQ2+0 ; If the remaining number of iterations for this sound BNE msco3 ; effect in sound byte #0 is non-zero, jump to msco3 to ; keep making the sound LDX soundByteSQ2+12 ; If byte #12 of the sound effect data is non-zero, then BNE msco3 ; this sound effect keeps looping, so jump to msco3 to ; keep making the sound LDA enableSound ; If enableSound = 0 then sound is disabled, so jump to BEQ msco2 ; msco2 to silence the SQ2 channel and return from the ; subroutine ; If we get here then we have finished making the sound ; effect, so we now send the volume and pitch values for ; the music to the APU, so if there is any music playing ; it will pick up again, and we mark this sound channel ; as clear of sound effects LDA sq2Volume ; Send sq2Volume to the APU via SQ2_VOL, which is the STA SQ2_VOL ; volume byte of any music that was playing when the ; sound effect took precedence LDA sq2Lo ; Send (sq2Hi sq2Lo) to the APU via (SQ2_HI SQ2_LO), STA SQ2_LO ; which is the pitch of any music that was playing when LDA sq2Hi ; the sound effect took precedence STA SQ2_HI STX effectOnSQ2 ; Set effectOnSQ2 = 0 to mark the SQL channel as clear ; of sound effects, so the channel can be used for music ; and is ready for the next sound effect RTS ; Return from the subroutine .msco2 ; If we get here then sound is disabled, so we need to ; silence the SQ2 channel LDA #%00110000 ; Set the volume of the SQ2 channel to zero as follows: STA SQ2_VOL ; ; * Bits 6-7 = duty pulse length is 3 ; * Bit 5 set = infinite play ; * Bit 4 set = constant volume ; * Bits 0-3 = volume is 0 STX effectOnSQ2 ; Set effectOnSQ2 = 0 to mark the SQL channel as clear ; of sound effects, so the channel can be used for music ; and is ready for the next sound effect RTS ; Return from the subroutine .msco3 ; If we get here then we need to keep making the sound ; effect on channel SQ2 DEC soundByteSQ2+0 ; Decrement the remaining length of the sound in byte #0 ; as we are about to make the sound for another ; iteration DEC soundVolCountSQ2 ; Decrement the volume envelope counter so we count down ; towards the point where we apply the volume envelope BNE msco5 ; If the volume envelope counter has not reached zero ; then jump to msco5, as we don't apply the next entry ; from the volume envelope yet ; If we get here then the counter in soundVolCountSQ2 ; just reached zero, so we apply the next entry from the ; volume envelope LDA soundByteSQ2+11 ; Reset the volume envelope counter to byte #11 from the STA soundVolCountSQ2 ; sound effect's data, which controls how often we apply ; the volume envelope to the sound effect LDY soundVolIndexSQ2 ; Set Y to the index of the current byte in the volume ; envelope LDA soundVolumeSQ2 ; Set soundAddr(1 0) = soundVolumeSQ2(1 0) STA soundAddr ; LDA soundVolumeSQ2+1 ; So soundAddr(1 0) contains the address of the volume STA soundAddr+1 ; envelope for this sound effect LDA (soundAddr),Y ; Set A to the data byte at the current index in the ; volume envelope BPL msco4 ; If bit 7 is clear then we just fetched a volume value ; from the envelope, so jump to msco4 to apply it ; If we get here then A must be $80 or $FF, as those are ; the only two valid entries in the volume envelope that ; have bit 7 set ; ; $80 means we loop back to the start of the envelope, ; while $FF means the envelope ends here CMP #$80 ; If A is not $80 then we must have just fetched $FF BNE msco5 ; from the envelope, so jump to msco5 to exit the ; envelope ; If we get here then we just fetched a $80 from the ; envelope data, so we now loop around to the start of ; the envelope, so it keeps repeating LDY #0 ; Set Y to zero so we fetch data from the start of the ; envelope again LDA (soundAddr),Y ; Set A to the byte of envelope data at index 0, so we ; can fall through into msco4 to process it .msco4 ; If we get here then A contains an entry from the ; volume envelope for this sound effect, so now we send ; it to the APU to change the volume ORA soundByteSQ2+6 ; OR the envelope byte with the sound effect's byte #6, STA SQ2_VOL ; and send the result to the APU via SQ2_VOL ; ; Data bytes in the volume envelope data only use the ; low nibble (the high nibble is only used to mark the ; end of the data), and the sound effect's byte #6 only ; uses the high nibble, so this sets the low nibble of ; the APU byte to the volume level from the data, and ; the high nibble of the APU byte to the configuration ; in byte #6 (which sets the duty pulse, looping and ; constant flags for the volume) INY ; Increment the index of the current byte in the volume STY soundVolIndexSQ2 ; envelope so on the next iteration we move on to the ; next byte in the envelope .msco5 ; Now that we are done with the volume envelope, it's ; time to move on to the pitch of the sound effect LDA soundPitCountSQ2 ; If the byte #1 counter has not yet run down to zero, BNE msco8 ; jump to msco8 to skip the following, so we don't send ; pitch data to the APU on this iteration ; If we get here then the counter in soundPitCountSQ2 ; (which counts down from the value of byte #1) has run ; down to zero, so we now send pitch data to the ALU if ; if we haven't yet sent it all LDA soundByteSQ2+12 ; If byte #12 is non-zero then the sound effect loops BNE msco6 ; infinitely, so jump to msco6 to send pitch data to the ; APU LDA soundByteSQ2+9 ; Otherwise, if the counter in byte #9 has not run down BNE msco6 ; then we haven't yet sent pitch data for enough ; iterations, so jump to msco6 to send pitch data to the ; APU RTS ; Return from the subroutine .msco6 ; If we get here then we are sending pitch data to the ; APU on this iteration, so now we do just that DEC soundByteSQ2+9 ; Decrement the counter in byte #9, which contains the ; number of iterations for which we send pitch data to ; the APU (as that's what we are doing) LDA soundByteSQ2+1 ; Reset the soundPitCountSQ2 counter to the value of STA soundPitCountSQ2 ; byte #1 so it can start counting down again to trigger ; the next pitch change after this one LDA soundByteSQ2+2 ; Set A to the low byte of the sound effect's current ; pitch, which is in byte #2 of the sound data LDX soundByteSQ2+7 ; If byte #7 is zero then vibrato is disabled, so jump BEQ msco7 ; to msco7 to skip the following instruction ADC soundVibrato ; Byte #7 is non-zero, so add soundVibrato to the pitch ; of the sound in A to apply vibrato (this also adds the ; C flag, which is not in a fixed state, so this adds an ; extra level of randomness to the vibrato effect) .msco7 STA soundLoSQ2 ; Store the value of A (i.e. the low byte of the sound ; effect's pitch, possibly with added vibrato) in ; soundLoSQ2 STA SQ2_LO ; Send the value of soundLoSQ2 to the APU via SQ2_LO LDA soundByteSQ2+3 ; Set A to the high byte of the sound effect's current ; pitch, which is in byte #3 of the sound data STA soundHiSQ2 ; Store the value of A (i.e. the high byte of the sound ; effect's pitch) in soundHiSQ2 STA SQ2_HI ; Send the value of soundHiSQ2 to the APU via SQ2_HI .msco8 DEC soundPitCountSQ2 ; Decrement the byte #1 counter, as we have now done one ; more iteration of the sound effect LDA soundByteSQ2+13 ; If byte #13 of the sound data is zero then we apply BEQ msco9 ; pitch variation in every iteration (if enabled), so ; jump to msco9 to skip the following and move straight ; to the pitch variation checks DEC soundPitchEnvSQ2 ; Otherwise decrement the byte #13 counter to count down ; towards the point where we apply pitch variation BNE msco11 ; If the counter is not yet zero, jump to msco11 to ; return from the subroutine without applying pitch ; variation, as the counter has not yet reached that ; point ; If we get here then the byte #13 counter just ran down ; to zero, so we need to apply pitch variation (if ; enabled) STA soundPitchEnvSQ2 ; Reset the soundPitchEnvSQ2 counter to the value of ; byte #13 so it can start counting down again, for the ; next pitch variation after this one .msco9 LDA soundByteSQ2+8 ; Set A to byte #8 of the sound data, which determines ; whether pitch variation is enabled BEQ msco11 ; If A is zero then pitch variation is not enabled, so ; jump to msco11 to return from the subroutine without ; applying pitch variation ; If we get here then pitch variation is enabled, so now ; we need to apply it BMI msco10 ; If A is negative then we need to add the value to the ; pitch's period, so jump to msco10 ; If we get here then we need to subtract the 16-bit ; value in bytes #4 and #5 from the pitch's period in ; (soundHiSQ2 soundLoSQ2) ; ; Reducing the pitch's period increases its frequency, ; so this makes the note frequency higher LDA soundLoSQ2 ; Subtract the 16-bit value in bytes #4 and #5 of the SEC ; sound data from (soundHiSQ2 soundLoSQ2), updating SBC soundByteSQ2+4 ; (soundHiSQ2 soundLoSQ2) to the result, and sending STA soundLoSQ2 ; it to the APU via (SQ2_HI SQ2_LO) STA SQ2_LO ; LDA soundHiSQ2 ; Note that bits 2 to 7 of the high byte are cleared so SBC soundByteSQ2+5 ; the length counter does not reload AND #%00000011 STA soundHiSQ2 STA SQ2_HI RTS ; Return from the subroutine .msco10 ; If we get here then we need to add the 16-bit value ; in bytes #4 and #5 to the pitch's period in ; (soundHiSQ2 soundLoSQ2) ; ; Increasing the pitch's period reduces its frequency, ; so this makes the note frequency lower LDA soundLoSQ2 ; Add the 16-bit value in bytes #4 and #5 of the sound CLC ; data to (soundHiSQ2 soundLoSQ2), updating ADC soundByteSQ2+4 ; (soundHiSQ2 soundLoSQ2) to the result, and sending STA soundLoSQ2 ; it to the APU via (SQ2_HI SQ2_LO) STA SQ2_LO ; LDA soundHiSQ2 ; Note that bits 2 to 7 of the high byte are cleared so ADC soundByteSQ2+5 ; the length counter does not reload AND #%00000011 STA soundHiSQ2 STA SQ2_HI .msco11 RTS ; Return from the subroutine
Name: MakeSoundOnNOISE [Show more] Type: Subroutine Category: Sound Summary: Make the current sound effect on the NOISE channel Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeSound calls MakeSoundOnNOISE
.MakeSoundOnNOISE LDA effectOnNOISE ; If effectOnNOISE is non-zero then a sound effect is BNE msct1 ; being made on channel NOISE, so jump to msct1 to keep ; making it RTS ; Otherwise return from the subroutine .msct1 LDA soundByteNOISE+0 ; If the remaining number of iterations for this sound BNE msct3 ; effect in sound byte #0 is non-zero, jump to msct3 to ; keep making the sound LDX soundByteNOISE+12 ; If byte #12 of the sound effect data is non-zero, then BNE msct3 ; this sound effect keeps looping, so jump to msct3 to ; keep making the sound LDA enableSound ; If enableSound = 0 then sound is disabled, so jump to BEQ msct2 ; msct2 to silence the NOISE channel and return from the ; subroutine ; If we get here then we have finished making the sound ; effect, so we now send the volume and pitch values for ; the music to the APU, so if there is any music playing ; it will pick up again, and we mark this sound channel ; as clear of sound effects LDA noiseVolume ; Send noiseVolume to the APU via NOISE_VOL, which is STA NOISE_VOL ; the volume byte of any music that was playing when the ; sound effect took precedence LDA noiseLo ; Send (noiseHi noiseLo) to the APU via NOISE_LO, which STA NOISE_LO ; is the pitch of any music that was playing when the ; sound effect took precedence STX effectOnNOISE ; Set effectOnNOISE = 0 to mark the SQL channel as clear ; of sound effects, so the channel can be used for music ; and is ready for the next sound effect RTS ; Return from the subroutine .msct2 ; If we get here then sound is disabled, so we need to ; silence the NOISE channel LDA #%00110000 ; Set the volume of the NOISE channel to zero as STA NOISE_VOL ; follows: ; ; * Bits 6-7 = duty pulse length is 3 ; * Bit 5 set = infinite play ; * Bit 4 set = constant volume ; * Bits 0-3 = volume is 0 STX effectOnNOISE ; Set effectOnNOISE = 0 to mark the SQL channel as clear ; of sound effects, so the channel can be used for music ; and is ready for the next sound effect RTS ; Return from the subroutine .msct3 ; If we get here then we need to keep making the sound ; effect on channel NOISE DEC soundByteNOISE+0 ; Decrement the remaining length of the sound in byte #0 ; as we are about to make the sound for another ; iteration DEC soundVolCountNOISE ; Decrement the volume envelope counter so we count down ; towards the point where we apply the volume envelope BNE msct5 ; If the volume envelope counter has not reached zero ; then jump to msct5, as we don't apply the next entry ; from the volume envelope yet ; If we get here then the counter in soundVolCountNOISE ; just reached zero, so we apply the next entry from the ; volume envelope LDA soundByteNOISE+11 ; Reset the volume envelope counter to byte #11 from the STA soundVolCountNOISE ; sound effect's data, which controls how often we apply ; the volume envelope to the sound effect LDY soundVolIndexNOISE ; Set Y to the index of the current byte in the volume ; envelope LDA soundVolumeNOISE ; Set soundAddr(1 0) = soundVolumeNOISE(1 0) STA soundAddr ; LDA soundVolumeNOISE+1 ; So soundAddr(1 0) contains the address of the volume STA soundAddr+1 ; envelope for this sound effect LDA (soundAddr),Y ; Set A to the data byte at the current index in the ; volume envelope BPL msct4 ; If bit 7 is clear then we just fetched a volume value ; from the envelope, so jump to msct4 to apply it ; If we get here then A must be $80 or $FF, as those are ; the only two valid entries in the volume envelope that ; have bit 7 set ; ; $80 means we loop back to the start of the envelope, ; while $FF means the envelope ends here CMP #$80 ; If A is not $80 then we must have just fetched $FF BNE msct5 ; from the envelope, so jump to msct5 to exit the ; envelope ; If we get here then we just fetched a $80 from the ; envelope data, so we now loop around to the start of ; the envelope, so it keeps repeating LDY #0 ; Set Y to zero so we fetch data from the start of the ; envelope again LDA (soundAddr),Y ; Set A to the byte of envelope data at index 0, so we ; can fall through into msct4 to process it .msct4 ; If we get here then A contains an entry from the ; volume envelope for this sound effect, so now we send ; it to the APU to change the volume ORA soundByteNOISE+6 ; OR the envelope byte with the sound effect's byte #6, STA NOISE_VOL ; and send the result to the APU via NOISE_VOL ; ; Data bytes in the volume envelope data only use the ; low nibble (the high nibble is only used to mark the ; end of the data), and the sound effect's byte #6 only ; uses the high nibble, so this sets the low nibble of ; the APU byte to the volume level from the data, and ; the high nibble of the APU byte to the configuration ; in byte #6 (which sets the duty pulse, looping and ; constant flags for the volume) INY ; Increment the index of the current byte in the volume STY soundVolIndexNOISE ; envelope so on the next iteration we move on to the ; next byte in the envelope .msct5 ; Now that we are done with the volume envelope, it's ; time to move on to the pitch of the sound effect LDA soundPitCountNOISE ; If the byte #1 counter has not yet run down to zero, BNE msct8 ; jump to msct8 to skip the following, so we don't send ; pitch data to the APU on this iteration ; If we get here then the counter in soundPitCountNOISE ; (which counts down from the value of byte #1) has run ; down to zero, so we now send pitch data to the ALU if ; if we haven't yet sent it all LDA soundByteNOISE+12 ; If byte #12 is non-zero then the sound effect loops BNE msct6 ; infinitely, so jump to msct6 to send pitch data to the ; APU LDA soundByteNOISE+9 ; Otherwise, if the counter in byte #9 has not run down BNE msct6 ; then we haven't yet sent pitch data for enough ; iterations, so jump to msct6 to send pitch data to the ; APU RTS ; Return from the subroutine .msct6 ; If we get here then we are sending pitch data to the ; APU on this iteration, so now we do just that DEC soundByteNOISE+9 ; Decrement the counter in byte #9, which contains the ; number of iterations for which we send pitch data to ; the APU (as that's what we are doing) LDA soundByteNOISE+1 ; Reset the soundPitCountNOISE counter to the value of STA soundPitCountNOISE ; byte #1 so it can start counting down again to trigger ; the next pitch change after this one LDA soundByteNOISE+2 ; Set A to the low byte of the sound effect's current ; pitch, which is in byte #2 of the sound data LDX soundByteNOISE+7 ; If byte #7 is zero then vibrato is disabled, so jump BEQ msct7 ; to msct7 to skip the following instruction ADC soundVibrato ; Byte #7 is non-zero, so add soundVibrato to the pitch ; of the sound in A to apply vibrato (this also adds the ; C flag, which is not in a fixed state, so this adds an ; extra level of randomness to the vibrato effect) AND #%00001111 ; We extract the low nibble because the high nibble is ; ignored in NOISE_LO, except for bit 7, which we want ; to clear so the period of the random noise generation ; is normal and not shortened .msct7 STA soundLoNOISE ; Store the value of A (i.e. the low byte of the sound ; effect's pitch, possibly with added vibrato) in ; soundLoNOISE STA NOISE_LO ; Send the value of soundLoNOISE to the APU via NOISE_LO .msct8 DEC soundPitCountNOISE ; Decrement the byte #1 counter, as we have now done one ; more iteration of the sound effect LDA soundByteNOISE+13 ; If byte #13 of the sound data is zero then we apply BEQ msct9 ; pitch variation in every iteration (if enabled), so ; jump to msct9 to skip the following and move straight ; to the pitch variation checks DEC soundPitchEnvNOISE ; Otherwise decrement the byte #13 counter to count down ; towards the point where we apply pitch variation BNE msct11 ; If the counter is not yet zero, jump to msct11 to ; return from the subroutine without applying pitch ; variation, as the counter has not yet reached that ; point ; If we get here then the byte #13 counter just ran down ; to zero, so we need to apply pitch variation (if ; enabled) STA soundPitchEnvNOISE ; Reset the soundPitchEnvNOISE counter to the value of ; byte #13 so it can start counting down again, for the ; next pitch variation after this one .msct9 LDA soundByteNOISE+8 ; Set A to byte #8 of the sound data, which determines ; whether pitch variation is enabled BEQ msct11 ; If A is zero then pitch variation is not enabled, so ; jump to msct11 to return from the subroutine without ; applying pitch variation ; If we get here then pitch variation is enabled, so now ; we need to apply it BMI msct10 ; If A is negative then we need to add the value to the ; pitch's period, so jump to msct10 ; If we get here then we need to subtract the 8-bit ; value in byte #4 from the pitch's period in ; soundLoNOISE ; ; Reducing the pitch's period increases its frequency, ; so this makes the note frequency higher LDA soundLoNOISE ; Subtract the 8-bit value in byte #4 of the sound data SEC ; from soundLoNOISE, updating soundLoNOISE to the SBC soundByteNOISE+4 ; result, and sending it to the APU via NOISE_LO AND #$0F STA soundLoNOISE STA NOISE_LO RTS ; Return from the subroutine .msct10 ; If we get here then we need to add the 8-bit value in ; byte #4 to the pitch's period in soundLoNOISE ; ; Increasing the pitch's period reduces its frequency, ; so this makes the note frequency lower LDA soundLoNOISE ; Add the 8-bit value in byte #4 of the sound data to CLC ; soundLoNOISE, updating soundLoNOISE to the result, and ADC soundByteNOISE+4 ; sending it to the APU via NOISE_LO AND #$0F STA soundLoNOISE STA NOISE_LO .msct11 RTS ; Return from the subroutine
Name: UpdateVibratoSeeds [Show more] Type: Subroutine Category: Sound Summary: Update the sound seeds that are used to randomise the vibrato effect Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeSound calls UpdateVibratoSeeds
.UpdateVibratoSeeds LDA soundVibrato ; Set A to soundVibrato with all bits cleared except for AND #%01001000 ; bits 3 and 6 ADC #%00111000 ; Add %00111000, so if bit 3 of A is clear, we leave ; bit 6 alone, otherwise bit 6 gets flipped ; ; The C flag doesn't affect this calculation, as it ; will only affect bit 0, which we don't care about ASL A ; Set the C flag to bit 6 of A ASL A ; ; So the C flag is: ; ; * Bit 6 of soundVibrato if bit 3 of soundVibrato is ; clear ; ; * Bit 6 of soundVibrato flipped if bit 3 of ; soundVibrato is set ; ; Or, to put it another way: ; ; C = bit 6 of soundVibrato EOR bit 3 of soundVibrato ROL soundVibrato+3 ; Rotate soundVibrato(0 1 2 3) left, inserting the C ROL soundVibrato+2 ; flag into bit 0 of soundVibrato+3 ROL soundVibrato+1 ROL soundVibrato RTS ; Return from the subroutine
Name: soundData [Show more] Type: Variable Category: Sound Summary: Sound data for the sound effects Deep dive: Sound effects in NES Elite
Context: See this variable on its own page References: This variable is used as follows: * StartEffectOnNOISE uses soundData * StartEffectOnSQ1 uses soundData * StartEffectOnSQ2 uses soundData
.soundData EQUW soundData0 EQUW soundData1 EQUW soundData2 EQUW soundData3 EQUW soundData4 EQUW soundData5 EQUW soundData6 EQUW soundData7 EQUW soundData8 EQUW soundData9 EQUW soundData10 EQUW soundData11 EQUW soundData12 EQUW soundData13 EQUW soundData14 EQUW soundData15 EQUW soundData16 EQUW soundData17 EQUW soundData18 EQUW soundData19 EQUW soundData20 EQUW soundData21 EQUW soundData22 EQUW soundData23 EQUW soundData24 EQUW soundData25 EQUW soundData26 EQUW soundData27 EQUW soundData28 EQUW soundData29 EQUW soundData30 EQUW soundData31 .soundData0 EQUB $3C, $03, $04, $00, $02, $00, $30, $00 EQUB $01, $0A, $00, $05, $00, $63 .soundData1 EQUB $16, $04, $A8, $00, $04, $00, $70, $00 EQUB $FF, $63, $0C, $02, $00, $00 .soundData2 EQUB $19, $19, $AC, $03, $1C, $00, $30, $00 EQUB $01, $63, $06, $02, $FF, $00 .soundData3 EQUB $05, $63, $2C, $00, $00, $00, $70, $00 EQUB $00, $63, $0C, $01, $00, $00 .soundData4 EQUB $09, $63, $57, $02, $02, $00, $B0, $00 EQUB $FF, $63, $08, $01, $00, $00 .soundData5 EQUB $0A, $02, $18, $00, $01, $00, $30, $FF EQUB $FF, $0A, $0C, $01, $00, $00 .soundData6 EQUB $0D, $02, $28, $00, $01, $00, $70, $FF EQUB $FF, $0A, $0C, $01, $00, $00 .soundData7 EQUB $19, $1C, $00, $01, $06, $00, $70, $00 EQUB $01, $63, $06, $02, $00, $00 .soundData8 EQUB $5A, $09, $14, $00, $01, $00, $30, $00 EQUB $FF, $63, $00, $0B, $00, $00 .soundData9 EQUB $46, $28, $02, $00, $01, $00, $30, $00 EQUB $FF, $00, $08, $06, $00, $03 .soundData10 EQUB $0E, $03, $6C, $00, $21, $00, $B0, $00 EQUB $FF, $63, $0C, $02, $00, $00 .soundData11 EQUB $13, $0F, $08, $00, $01, $00, $30, $00 EQUB $FF, $00, $0C, $03, $00, $02 .soundData12 EQUB $AA, $78, $1F, $00, $01, $00, $30, $00 EQUB $01, $00, $01, $08, $00, $0A .soundData13 EQUB $59, $02, $4F, $00, $29, $00, $B0, $FF EQUB $01, $FF, $00, $09, $00, $00 .soundData14 EQUB $19, $05, $82, $01, $29, $00, $B0, $FF EQUB $FF, $FF, $08, $02, $00, $00 .soundData15 EQUB $22, $05, $82, $01, $29, $00, $B0, $FF EQUB $FF, $FF, $08, $03, $00, $00 .soundData16 EQUB $0F, $63, $B0, $00, $20, $00, $70, $00 EQUB $FF, $63, $08, $02, $00, $00 .soundData17 EQUB $0D, $63, $8F, $01, $31, $00, $30, $00 EQUB $FF, $63, $10, $02, $00, $00 .soundData18 EQUB $18, $05, $FF, $01, $31, $00, $30, $00 EQUB $FF, $63, $10, $03, $00, $00 .soundData19 EQUB $46, $03, $42, $03, $29, $00, $B0, $00 EQUB $FF, $FF, $0C, $06, $00, $00 .soundData20 EQUB $0C, $02, $57, $00, $14, $00, $B0, $00 EQUB $FF, $63, $0C, $01, $00, $00 .soundData21 EQUB $82, $46, $0F, $00, $01, $00, $B0, $00 EQUB $01, $00, $01, $07, $00, $05 .soundData22 EQUB $82, $46, $00, $00, $01, $00, $B0, $00 EQUB $FF, $00, $01, $07, $00, $05 .soundData23 EQUB $19, $05, $82, $01, $29, $00, $B0, $FF EQUB $FF, $FF, $0E, $02, $00, $00 .soundData24 EQUB $AA, $78, $1F, $00, $01, $00, $30, $00 EQUB $01, $00, $01, $08, $00, $0A .soundData25 EQUB $14, $03, $08, $00, $01, $00, $30, $00 EQUB $FF, $FF, $00, $02, $00, $00 .soundData26 EQUB $01, $00, $00, $00, $00, $00, $30, $00 EQUB $00, $00, $0D, $00, $00, $00 .soundData27 EQUB $19, $05, $82, $01, $29, $00, $B0, $FF EQUB $FF, $FF, $0F, $02, $00, $00 .soundData28 EQUB $0B, $04, $42, $00, $08, $00, $B0, $00 EQUB $01, $63, $08, $01, $00, $02 .soundData29 EQUB $96, $1C, $00, $01, $06, $00, $70, $00 EQUB $01, $63, $06, $02, $00, $00 .soundData30 EQUB $96, $1C, $00, $01, $06, $00, $70, $00 EQUB $01, $63, $06, $02, $00, $00 .soundData31 EQUB $14, $02, $28, $00, $01, $00, $70, $FF EQUB $FF, $0A, $00, $02, $00, $00
Name: soundVolume [Show more] Type: Variable Category: Sound Summary: Volume envelope data for the sound effects Deep dive: Sound effects in NES Elite
Context: See this variable on its own page References: This variable is used as follows: * StartEffectOnNOISE uses soundVolume * StartEffectOnSQ1 uses soundVolume * StartEffectOnSQ2 uses soundVolume
.soundVolume EQUW soundVolume0 EQUW soundVolume1 EQUW soundVolume2 EQUW soundVolume3 EQUW soundVolume4 EQUW soundVolume5 EQUW soundVolume6 EQUW soundVolume7 EQUW soundVolume8 EQUW soundVolume9 EQUW soundVolume10 EQUW soundVolume11 EQUW soundVolume12 EQUW soundVolume13 EQUW soundVolume14 EQUW soundVolume15 EQUW soundVolume16 .soundVolume0 EQUB $0F, $0D, $0B, $09, $07, $05, $03, $01 EQUB $00, $FF .soundVolume1 EQUB $03, $05, $07, $09, $0A, $0C, $0E, $0E EQUB $0E, $0C, $0C, $0A, $0A, $09, $09, $07 EQUB $06, $05, $04, $03, $02, $02, $01, $FF .soundVolume2 EQUB $02, $06, $08, $00, $FF .soundVolume3 EQUB $06, $08, $0A, $0B, $0C, $0B, $0A, $09 EQUB $08, $07, $06, $05, $04, $03, $02, $01 EQUB $FF .soundVolume4 EQUB $01, $03, $06, $08, $0C, $80 .soundVolume5 EQUB $01, $04, $09, $0D, $80 .soundVolume6 EQUB $01, $04, $07, $09, $FF .soundVolume7 EQUB $09, $80 .soundVolume8 EQUB $0E, $0C, $0B, $09, $07, $05, $04, $03 EQUB $02, $01, $FF .soundVolume9 EQUB $0C, $00, $00, $0C, $00, $00, $FF .soundVolume10 EQUB $0B, $80 .soundVolume11 EQUB $0A, $0B, $0C, $0D, $0C, $80 .soundVolume12 EQUB $0C, $0A, $09, $07, $05, $04, $03, $02 EQUB $01, $FF .soundVolume13 EQUB $00, $FF .soundVolume14 EQUB $04, $05, $06, $06, $05, $04, $03, $02 EQUB $01, $FF .soundVolume15 EQUB $06, $05, $04, $03, $02, $01, $FF .soundVolume16 EQUB $0C, $0A, $09, $07, $05, $05, $04, $04 EQUB $03, $03, $02, $02, $01, $01, $FF
Name: volumeEnvelope [Show more] Type: Variable Category: Sound Summary: Volume envelope data for the game music Deep dive: Music in NES Elite
Context: See this variable on its own page References: No direct references to this variable in this source file
.volumeEnvelope .volumeEnvelopeLo EQUB LO(volumeEnvelope0) EQUB LO(volumeEnvelope1) EQUB LO(volumeEnvelope2) EQUB LO(volumeEnvelope3) EQUB LO(volumeEnvelope4) EQUB LO(volumeEnvelope5) EQUB LO(volumeEnvelope6) EQUB LO(volumeEnvelope7) EQUB LO(volumeEnvelope8) EQUB LO(volumeEnvelope9) EQUB LO(volumeEnvelope10) EQUB LO(volumeEnvelope11) EQUB LO(volumeEnvelope12) EQUB LO(volumeEnvelope13) EQUB LO(volumeEnvelope14) EQUB LO(volumeEnvelope15) EQUB LO(volumeEnvelope16) EQUB LO(volumeEnvelope17) EQUB LO(volumeEnvelope18) EQUB LO(volumeEnvelope19) .volumeEnvelopeHi EQUB HI(volumeEnvelope0) EQUB HI(volumeEnvelope1) EQUB HI(volumeEnvelope2) EQUB HI(volumeEnvelope3) EQUB HI(volumeEnvelope4) EQUB HI(volumeEnvelope5) EQUB HI(volumeEnvelope6) EQUB HI(volumeEnvelope7) EQUB HI(volumeEnvelope8) EQUB HI(volumeEnvelope9) EQUB HI(volumeEnvelope10) EQUB HI(volumeEnvelope11) EQUB HI(volumeEnvelope12) EQUB HI(volumeEnvelope13) EQUB HI(volumeEnvelope14) EQUB HI(volumeEnvelope15) EQUB HI(volumeEnvelope16) EQUB HI(volumeEnvelope17) EQUB HI(volumeEnvelope18) EQUB HI(volumeEnvelope19) .volumeEnvelope0 EQUB $01, $0A, $0F, $0C, $8A .volumeEnvelope1 EQUB $01, $0A, $0F, $0B, $09, $87 .volumeEnvelope2 EQUB $01, $0E, $0C, $09, $07, $0B, $0A, $07 EQUB $05, $09, $07, $05, $04, $07, $06, $04 EQUB $03, $05, $04, $03, $02, $03, $02, $01 EQUB $80 .volumeEnvelope3 EQUB $01, $0E, $0D, $0B, $09, $07, $0C, $0B EQUB $09, $07, $05, $0A, $09, $07, $05, $03 EQUB $08, $07, $05, $03, $02, $06, $05, $03 EQUB $02, $80 .volumeEnvelope4 EQUB $01, $0A, $0D, $0A, $09, $08, $07, $86 .volumeEnvelope5 EQUB $01, $08, $0B, $09, $07, $05, $83 .volumeEnvelope6 EQUB $01, $0A, $0D, $0C, $0B, $09, $87 .volumeEnvelope7 EQUB $01, $06, $08, $07, $05, $03, $81 .volumeEnvelope8 EQUB $0A, $0D, $0C, $0B, $0A, $09, $08, $07 EQUB $06, $05, $04, $03, $02, $81 .volumeEnvelope9 EQUB $02, $0E, $0D, $0C, $0B, $0A, $09, $08 EQUB $07, $06, $05, $04, $03, $02, $81 .volumeEnvelope10 EQUB $01, $0E, $0D, $0C, $0B, $0A, $09, $08 EQUB $07, $06, $05, $04, $03, $02, $81 .volumeEnvelope11 EQUB $01, $0E, $0C, $09, $07, $05, $04, $03 EQUB $02, $81 .volumeEnvelope12 EQUB $01, $0D, $0C, $0A, $07, $06, $05, $04 EQUB $03, $02, $81 .volumeEnvelope13 EQUB $01, $0D, $0B, $09, $07, $05, $04, $03 EQUB $02, $81 .volumeEnvelope14 EQUB $01, $0D, $07, $01, $80 .volumeEnvelope15 EQUB $01, $00, $80 .volumeEnvelope16 EQUB $01, $09, $02, $80 .volumeEnvelope17 EQUB $01, $0A, $01, $05, $02, $01, $80 .volumeEnvelope18 EQUB $01, $0D, $01, $07, $02, $01, $80 .volumeEnvelope19 EQUB $01, $0F, $0D, $0B, $89
Name: pitchEnvelope [Show more] Type: Variable Category: Sound Summary: Pitch envelope data for the game music Deep dive: Music in NES Elite
Context: See this variable on its own page References: No direct references to this variable in this source file
.pitchEnvelope .pitchEnvelopeLo EQUB LO(pitchEnvelope0) EQUB LO(pitchEnvelope1) EQUB LO(pitchEnvelope2) EQUB LO(pitchEnvelope3) EQUB LO(pitchEnvelope4) EQUB LO(pitchEnvelope5) EQUB LO(pitchEnvelope6) EQUB LO(pitchEnvelope7) .pitchEnvelopeHi EQUB HI(pitchEnvelope0) EQUB HI(pitchEnvelope1) EQUB HI(pitchEnvelope2) EQUB HI(pitchEnvelope3) EQUB HI(pitchEnvelope4) EQUB HI(pitchEnvelope5) EQUB HI(pitchEnvelope6) EQUB HI(pitchEnvelope7) .pitchEnvelope0 EQUB $00, $80 .pitchEnvelope1 EQUB $00, $01, $02, $01, $00, $FF, $FE, $FF EQUB $80 .pitchEnvelope2 EQUB $00, $02, $00, $FE, $80 .pitchEnvelope3 EQUB $00, $01, $00, $FF, $80 .pitchEnvelope4 EQUB $00, $04, $00, $04, $00, $80 .pitchEnvelope5 EQUB $00, $02, $04, $02, $00, $FE, $FC, $FE EQUB $80 .pitchEnvelope6 EQUB $00, $03, $06, $03, $00, $FD, $FA, $FD EQUB $80 .pitchEnvelope7 EQUB $00, $04, $08, $04, $00, $FC, $F8, $FC EQUB $80