Mugi
Member
First of all, i take no credit from this, i didn't even know this issue existed until a friend of mine who was poking around with my sources noticed it,
and secondly, the code for this is available in nesdev, from where it was almost directly copypasted.
I'm just sharing this since it's something that should exist in the engine by default and will make PAL people's life easier.
what this script does is sets the engine to autodetect the system you're running on and properly set the audio driver to PAL/NTSC/dendy mode to make the sound play as intended regardless of the region the game is being played in.
by default, nesmaker roms are hardcoded into NTSC mode, and playing such a rom in a PAL console will slow down the audio and make it sound different than it should sound.
first of all, you make a new .asm file: getTVSystem.asm
and save it in: GameEngineData\Routines\Basic\InitializationScripts\getTVSystem.asm
next you go to GameEngineData\Routines\Basic\BankData and open Bank1B.asm.
replace the contents of the file with the following:
and lastly, open Routines\Basic\MainASM.asm and replace it's contents with the following:
i dont have a hardware cartridge or a flasher to test this on real hardware, but this was tested to function correctly in mesen, fceux and fixnes and it does indeed function as intended and keeps the music properly playing even if you set the emulator
to run in PAL (50Hz) mode.
and secondly, the code for this is available in nesdev, from where it was almost directly copypasted.
I'm just sharing this since it's something that should exist in the engine by default and will make PAL people's life easier.
what this script does is sets the engine to autodetect the system you're running on and properly set the audio driver to PAL/NTSC/dendy mode to make the sound play as intended regardless of the region the game is being played in.
by default, nesmaker roms are hardcoded into NTSC mode, and playing such a rom in a PAL console will slow down the audio and make it sound different than it should sound.
first of all, you make a new .asm file: getTVSystem.asm
Code:
;
; NES TV system detection code
; Copyright 2011 Damian Yerrick
;
; Copying and distribution of this file, with or without
; modification, are permitted in any medium without royalty provided
; the copyright notice and this notice are preserved in all source
; code copies. This file is offered as-is, without any warranty.
;
;.export getTVSystem
; using vBlankTimer as nmis
;.importzp nmis
;.align 32 ; ensure that branches do not cross a page boundary
;;
; Detects which of NTSC, PAL, or Dendy is in use by counting cycles
; between NMIs.
;
; NTSC NES produces 262 scanlines, with 341/3 CPU cycles per line.
; PAL NES produces 312 scanlines, with 341/3.2 CPU cycles per line.
; Its vblank is longer than NTSC, and its CPU is slower.
; Dendy is a Russian famiclone distributed by Steepler that uses the
; PAL signal with a CPU as fast as the NTSC CPU. Its vblank is as
; long as PAL's, but its NMI occurs toward the end of vblank (line
; 291 instead of 241) so that cycle offsets from NMI remain the same
; as NTSC, keeping Balloon Fight and any game using a CPU cycle-
; counting mapper (e.g. FDS, Konami VRC) working.
;
; nmis is a variable that the NMI handler modifies every frame.
; Make sure your NMI handler finishes within 1500 or so cycles (not
; taking the whole NMI or waiting for sprite 0) while calling this,
; or the result in A will be wrong.
;
; @return A: TV system (0: NTSC, 1: PAL, 2: Dendy; 3: unknown
; Y: high byte of iterations used (1 iteration = 11 cycles)
; X: low byte of iterations used
getTVSystem:
ldx #0
ldy #0
lda vBlankTimer
nmiwait1:
cmp vBlankTimer
beq nmiwait1
lda vBlankTimer
nmiwait2:
; Each iteration takes 11 cycles.
; NTSC NES: 29780 cycles or 2707 = $A93 iterations
; PAL NES: 33247 cycles or 3022 = $BCE iterations
; Dendy: 35464 cycles or 3224 = $C98 iterations
; so we can divide by $100 (rounding down), subtract ten,
; and end up with 0=ntsc, 1=pal, 2=dendy, 3=unknown
inx
bne nminoiny
iny
nminoiny:
cmp vBlankTimer
beq nmiwait2
tya
sec
sbc #10
cmp #3
bcc notAbove3
lda #3
notAbove3:
rts
and save it in: GameEngineData\Routines\Basic\InitializationScripts\getTVSystem.asm
next you go to GameEngineData\Routines\Basic\BankData and open Bank1B.asm.
replace the contents of the file with the following:
Code:
;PAL / NTSC Detection for GGsound
.include "Routines\Basic\InitializationScripts\getTVSystem.asm"
;Music Bank
.include ROOT\System\ggsound.asm
IntroSong:
.include "Sound\AllSongs_WithSFX.asm"
and lastly, open Routines\Basic\MainASM.asm and replace it's contents with the following:
Code:
;1. iNES header
.include "GameData\Constants.asm"
.include ROOT\System\Header.asm
;2. Constants and variables
;A. First, the constants
.include "Sound\ggsound.inc"
.include "GameData\macroList.asm
.include "ScreenData\init.ini"
.include SCR_MEMORY_MAP
;C. then the BANK ASSIGNMENTS
.include ROOT\System\AssignBanks.asm
;________________________________________
;4 The Reset
.include ROOT\System\Reset.asm
;; initialize
.include ROOT\InitializationScripts\InitLoads.asm
_____________
;Things are initialized. Jump to the main game loop
;____________________________________________________
LDA #$00
STA gameState
STA textboxHandler
LDY #BANK_MUSIC
JSR bankswitchY
lda #1
sta skipNMI
jsr getTVSystem ; loads region instead of hardcoding it
;lda #SOUND_REGION_NTSC ;or #SOUND_REGION_PAL, or #SOUND_REGION_DENDY
sta sound_param_byte_0
lda #0
sta skipNMI
lda #<(song_list)
sta sound_param_word_0
lda #>(song_list)
sta sound_param_word_0+1
lda #<(sfx_list)
sta sound_param_word_1
lda #>(sfx_list)
sta sound_param_word_1+1
lda #<(instrument_list)
sta sound_param_word_2
lda #>(instrument_list)
sta sound_param_word_2+1
;lda #<dpcm_list
;sta sound_param_word_3
;lda #>dpcm_list
;sta sound_param_word_3+1
jsr sound_initialize
LDY prevBank
JSR bankswitchY
nevermindPlaySong:
LDA #SKIP_START_SCREEN
BEQ dontSkipStartScreen
;;;;
;;============================================================
;;SKIP START SCREEN!
;;; COMMENT THIS ALL OUT TO OBSERVE START SCREEN AGAIN
;;; this script does the same as our "press start" function does on start screen,
;;; except here, it does it automatically on startup
;LDA #SKIP_START_SCREEN
;BEQ dontSkipStartScreen
LDA #STATE_START_GAME
STA change_state
LDA #%10000000
STA gameHandler ;; turn sprites on
;;;;;;;;;
;;;;END SKIP START SCREEN!
;;============================================================
dontSkipStartScreen:
;PlaySong #$00
JMP MainGameLoop
WaitFrame:
inc sleeping
sleepLoop:
lda sleeping
BNE sleepLoop
RTS
;9 NMI
NMI:
;first push whatever is in the accumulator to the stack
PHA
LDA doNMI
BEQ dontSkipNMI
JMP skipWholeNMI
dontSkipNMI:
LDA #$01
STA doNMI
TXA
PHA
TYA
PHA
PHP
LDA temp
PHA ;STA NMItemp
LDA temp1
PHA ;STA NMItemp1
LDA temp2
PHA ;STA NMItemp2
LDA temp3
PHA ;STA NMItemp3
LDA tempx
PHA ;STA NMItempx
LDA tempy
PHA ;STA NMItempy
LDA tempz
PHA ;STA NMItempz
LDA updateHUD_fire_Address_Lo
PHA ;STA NMI_updateHud_AddLo
LDA updateHUD_fire_Address_Hi
PHA ;STA NMI_updateHud_AddHi
LDA updateHUD_fire_Tile
PHA ;STA updateHud_tile
LDA temp16
PHA ;STA NMItemp16
LDA temp16+1
PHA ;STA NMItemp16_plus_1
LDA currentBank
PHA ;STA NMIcurrentBank
LDA prevBank
PHA ;STA NMIprevBank
LDA tempBank
PHA ;STA nmiTempBank
LDA chrRamBank
PHA ;STA nmiChrRamBank
LDA skipNMI
BEQ dontSkipNMI2
JMP skipNMIstuff
dontSkipNMI2:
LDA #$00
STA $2000
LDA soft2001
STA $2001
;;;Set OAL DMA
LDA #$00
STA $2003
LDA #$02
STA $4014
;; Load the Palette
LDA soft2001
BNE doScreenUpdates
JMP skipScreenUpdates
doScreenUpdates:
bit $2002
LDA updatePalettes
BNE doPaletteUpdates
JMP +
doPaletteUpdates:
.include ROOT\DataLoadScripts\LoadPalettes.asm
JMP skipScreenUpdates
+
LDA updateNametable
BNE doUpdateNametable
JMP +
doUpdateNametable:;; for nametable changes
.include ROOT\DataLoadScripts\LoadTileUpdate.asm
JMP skipScreenUpdates
+
LDA UpdateAtt
BNE doUpdateAtt
JMP +
doUpdateAtt:
.include ROOT\DataLoadScripts\LoadAttUpdate.asm
JMP skipScreenUpdates
+
skipScreenUpdates:
;; always do hud update, otherwise
;; it's possible that updates to tiles, attributes, or palettes
;; will cause a skip in hud update.
.include ROOT\DataLoadScripts\LoadHudData.asm
LDA xScroll_hi
AND #%00000001
STA showingNametable
LDA #CHECK_SPRITE_ZERO
BEQ skipSprite0Check
LDA gameState
CMP #GS_MainGame
BNE skipSprite0Check
LDA gameHandler
AND #%10000000
BEQ skipSprite0Check
LDA $0200
CMP #SPRITE_ZERO_Y
BNE skipSprite0Check
;;; do sprite 0 check.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LDA #$00 ; start with no scroll for status bar
STA $2005
STA $2005
LDA #%10010000 ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
STA $2000 ; start with nametable = 0 for status bar
LDA #%00011110 ; enable sprites, enable background, no clipping on left side
STA $2001
;JMP +
WaitNotSprite0:
lda $2002
and #%01000000
bne WaitNotSprite0 ; wait until sprite 0 not hit
WaitSprite0:
lda $2002
and #%01000000
beq WaitSprite0 ; wait until sprite 0 is hit
ldx #SPRITE_ZERO_HBLANK ;;; SET THIS TO A DIFFERENT VALUE FOR OFFSET TO AVOID PIXEL FLICKER
WaitScanline:
dex
bne WaitScanline
LDA soft2001
STA $2001
skipSprite0Check:
LDA #%10010000
ORA showingNametable
STA $2000
LDA xScroll
STA $2005 ;reset scroll values to zero
LDA yScroll
STA $2005 ;reset scroll values to zero
skipNMIstuff:
DEC vBlankTimer
INC randomSeed1
;;return from this interrupt
;; music player things
LDA #$0
STA sleeping
LDA currentBank
STA prevBank
LDY #BANK_MUSIC ;; switch to music bank
JSR bankswitchY
soundengine_update
LDY prevBank
JSR bankswitchY
PLA
STA chrRamBank
PLA
STA tempBank
PLA
STA prevBank
PLA
STA currentBank
PLA
STA temp16+1
PLA
STA temp16
PLA
STA updateHUD_fire_Tile
PLA
STA updateHUD_fire_Address_Hi
PLA
STA updateHUD_fire_Address_Lo
PLA ;LDA NMItempz
STA tempz
PLA ;LDA NMItempy
STA tempy
PLA ;LDA NMItempx
STA tempx
PLA ;LDA NMItemp3
STA temp3
PLA ;LDA NMItemp2
STA temp2
PLA ;LDA NMItemp1
STA temp1
PLA ;LDA NMItemp
STA temp
LDA #$00
STA doNMI
PLP
PLA
TAY
PLA
TAX
skipWholeNMI:
PLA
RTI
MainGameLoop:
;;;;;;;;=========HANDLE FRAME TIMING
JSR GamePadCheck
LDA vBlankTimer
vBlankTimerLoop:
CMP vBlankTimer
BEQ vBlankTimerLoop
;;;;;;==========END HANDLE FRAME TIMING
;; always get input.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; handle stage changes.
LDA currentBank
STA prevBank
LDY #$14
JSR bankswitchY
JSR HandleStateChanges
; JSR HandleHudData
LDY prevBank
JSR bankswitchY
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
JSR HandleScreenLoads
JSR CheckForUpdateScreenData
;JSR HandleFadeLevels
;JSR HandleFades
;JSR HandleUpdateNametable
JSR HandleBoxUpdates
JSR HandleMusic
JSR HandleInput
JSR HandleUpdateObjects
JSR HandleRandomizing
JSR HandleGameTimer
HandleActivateWarpFlag
LDA activateWarpFlag
BEQ +
LDA #$00
STA activateWarpFlag
LDX player1_object
DeactivateCurrentObject
LDA #$01
STA loadObjectFlag
LDA warpMap
sta currentMap
clc
ADC #$01
STA temp
GoToScreen navToScreen, temp, #$02
JMP ++ ;; skip scroll
+
LDA loadingTilesFlag
BEQ dontUpdateColumn
TXA
STA tempx
JSR updateColumnTiles
LDX tempx
JMP ++
dontUpdateColumn:
LDA testFlagThing
BEQ +
LDA tempTileUpdate_lo
STA temp16
LDA tempTileUpdate_hi
STA temp16+1
LDA tempChangeTiles
STA updateTile_00
LDA tempChangeTiles+1
STA updateTile_01
LDA tempChangeTiles+2
STA updateTile_02
LDA tempChangeTiles+3
STA updateTile_03
JSR HandleUpdateNametable
LDA #$00
STA testFlagThing
STA tileCollisionFlag
+
JSR HandleScroll
++
;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;
JMP MainGameLoop
RTS
;;;;;;;;;;;;;;;;;;;;;
.include "GameData\ScriptTables.asm"
.include ROOT\System\IncludeSystemFunctions.asm
.include "GameData\HUD_DEFINES.dat"
columnTableLo:
.db #$00, #$02, #$04, #$06, #$08, #$0A, #$0C, #$0E
.db #$10, #$12, #$14, #$16, #$18, #$1A, #$1C, #$1E
.db #$00, #$02, #$04, #$06, #$08, #$0A, #$0C, #$0E
.db #$10, #$12, #$14, #$16, #$18, #$1A, #$1C, #$1E
columnTableHi:
.db #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20
.db #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20
.db #$24, #$24, #$24, #$24, #$24, #$24, #$24, #$24
.db #$24, #$24, #$24, #$24, #$24, #$24, #$24, #$24
attrColumnTableLo:
.db #$c0, #$c1, #$c2, #$c3, #$c4, #$c5, #$c6, #$c7
.db #$c0, #$c1, #$c2, #$c3, #$c4, #$c5, #$c6, #$c7
attrColumnTableHi:
.db #$23, #$23, #$23, #$23, #$23, #$23, #$23, #$23
.db #$27, #$27, #$27, #$27, #$27, #$27, #$27, #$27
bitwiseLut:
.db #%10000000, #%01000000, #%00100000, #%00010000, #%00001000, #%00000100, #%00000010, #%00000001
bitwiseLut16:
.db #%10000000, #%01000000, #%00100000, #%00010000, #%00001000, #%00000100, #%00000010, #%00000001
directionTable:
.db #%00110000, #%11110000, #%11000000, #%11100000, #%00100000, #%10100000, #%10000000, #%10100000
;12. Vectors
.include ROOT\System\Vectors.asm
i dont have a hardware cartridge or a flasher to test this on real hardware, but this was tested to function correctly in mesen, fceux and fixnes and it does indeed function as intended and keeps the music properly playing even if you set the emulator
to run in PAL (50Hz) mode.