Game project help and progress thread

A place where you can keep others updated about your NES-related projects through screenshots, videos or information in general.

Moderator: Moderators

User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

Tsutarja wrote:So, when the carry flag is set, SBC #$00 subtracts nothing, but if it's cleared, it will subtract #$01 instead?
Exactly. Whenever the carry is clear it will subtract one extra unit.

This is why we need to use SEC before subtractions, we don't want to accidentally subtract one more than we should from the first byte, we only want to do that to the other bytes, to propagate any possible borrow.
If this is right, does this work with ADC too?
Yes, the carry affects additions too, but in the opposite way. Whenever it's set, it adds one extra unit.

Again, this is why we use CLC before additions, we don't want to add one more than the actual number we're adding, we only want to propagate the carry to the other places.
So, they point the locations of the tables in ROM?
Yes, exactly like in your example.

The update routine itself sets the ScreenPointer and the MetatilePointer as necessary every time it's called, but you have to set LevelPointer whenever you start a new level.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

Okay, I've been trying to think of how to use the decoding subroutine as a loop to draw a screen, but it has been quiet difficult to think of a good way to make it work. I probably could make it work, but it would be very slow. I want to have the individual screen loader as a subroutine in a way that I can use it anytime by giving the address of the screen. If possible to have it so that it can draw both nametables. I guess column per frame is not too much to overflow the buffer. How should be doing this? (I don't know if this is simple or not, but I may be thinking too complicated again)
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

If you're doing things as I suggested, drawing the initial screen is as simple as this:

Code: Select all

	sec
	lda CameraX+0
	sbc #$20
	sta ColumnX+0
	lda CameraX+1
	sbc #$00
	sta ColumnX+1
	lda #$50 ;10 columns * 8 metatiles
	sta Counter
Loop:
	jsr DecodeMetatile
	;OMITTED: WRITE BUFFER TO VRAM
	dec Counter
	beq Done
	clc
	lda ColumnX+0
	adc #$04
	sta ColumnX+0
	lda ColumnX+1
	adc #$00
	sta ColumnX+1
Done:
You can't possibly think that reimplementing the entire decoding proccess is simpler than this loop.

If you make the VRAM updating its own subroutine, the omitted part is just another JSR. You'd use the same routine in the NMI. Subroutines are your friends, so don't repeat yourself unless you absolutely need to.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

tokumaru wrote:

Code: Select all

	sec
	lda CameraX+0
	sbc #$20
	sta ColumnX+0
	lda CameraX+1
	sbc #$00
	sta ColumnX+1
	lda #$50 ;10 columns * 8 metatiles
	sta Counter
Loop:
	jsr DecodeMetatile
	;OMITTED: WRITE BUFFER TO VRAM
	dec Counter
	beq Done
	clc
	lda ColumnX+0
	adc #$04
	sta ColumnX+0
	lda ColumnX+1
	adc #$00
	sta ColumnX+1
Done:
I just tested the code, but it seems like the SBC #$00 causes ColumnX+1 to underflow to #$FF (because camera position is #$00 on bot X and Y axis) and not drawing the screen because it's larger than the level length. According to FCEUX's debugger after the screen draw routine finished, the game goes to loop the IRQ routine infinitely for some reason (Interrupt disable flag is set so it shouldn't happen). I tried removing the SBCs, but then the ADCs will mess something up (at least it seems like it as far as my debugging skills goes), and the loop obviously doesn't work without them. I did try setting the camera position to #$20 to prevent the carry flag getting cleared and the ColumnX+1 getting underflowed, but that didn't help. I also did take the background update routine from NMI as a separate subroutine so I can use it at the omitted part like you suggested.
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

Hum... it's hard to debug this not knowing how things are organized in the ROM, but this loop is supposed to run before the game loop, with rendering turned off. You also don't want the NMI meddling with thing while this runs, so make sure to configure flags as necessary to keep the NMI handler from doing anything PPU related.

The Camera must be positioned already, as does the pointer to the level and the level length, all used by the DecodeMetatile subroutine. This is all I can think of at the moment, but look hard for anything that could be interfering with the variables used here.

If you want me to take a look at the ROM, I can.

The underflow you mentioned is expected, which is why we check whether the column is within the boundaries of the level inside the DecodeMetatile routine. ColumnX will be out of bounds for the first few iterations of the loop, due to CameraX being 0, but since ColumnX is incremented each iteration, it will eventually be within bounds.

Does the normal column rendering during scrolling work? Since you're reusing code from that, you must make sure that works properly.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

I think I'm going to make the sound engine next. How difficult would it be to implement "instruments" that have pre-defined volume and pitch envelopes? I think that would save some space over writing the envelopes in the song data.
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Game project help and progress thread

Post by tepples »

Most NES games' music engines use some sort of "instrument" to represent duty, volume, and pitch changes. Play around in FamiTracker to see one common method.
User avatar
za909
Posts: 249
Joined: Fri Jan 24, 2014 9:05 am
Location: Mijn hart woont al in Nederland

Re: Game project help and progress thread

Post by za909 »

Tsutarja wrote:I think I'm going to make the sound engine next. How difficult would it be to implement "instruments" that have pre-defined volume and pitch envelopes? I think that would save some space over writing the envelopes in the song data.
There are really only two options that come to my mind:
1. Implementing some sort of ADSR envelope in software, therefore calculating your volume and other changes with countdowns and selectable load values for these counters (Capcom games use this method)
2. Making a bunch of tables with all the raw values inteded to be consequently written to $4000 and $4004, and then writing the next item one after the other until the stream is terminated or halted, until the engine signals that it has read a note cut byte, which then runs the "release" part of the instrument.

As for pitch, you have to apply the deviation to the low period value once you have it, and you can also store this as a data stream. I do my vibrato in a cheap way (I guess) by using 4 different 16-byte long sequences, all containing a stream of signed relative deviations from the previous value. I can simply select which sequence to use by changing the upper 4 bits of my variable, the lower 4 bits are loaded into Y, and I wrap these bits back to 0 when they overflow.
Plus it also has a countdown from the start of the note, so I can make it automatically apply mid-note or whenever I want.
(I realise this is really out of context so it doesn't make much sense but hopefully you can see what I meant)

Code: Select all

  VibHandler:
  ; apply vibrato to a pulse channel
	lda pu1_vibenable,x
	bmi +thereisvib ; if the vibrato is disabled, don't waste time with this
	rts 

	+thereisvib:
	bit temp_0
	bvc +read ; keyon frame has to initialize the value reading vector
	ldy temp_6 ; store 4 high bits for the value reading
	lda pu1_vibtbl,y
	sta pu1_vibphase,x
	+read:
	ldy pu1_vibphase,x
	lda Vibrato_TBL,y
	sta pu1_vibvalue,x
	tya
	and #$0F ; if all 16 items are done, loop back to the first
	cmp #$0F
	bne +notatend
	tya 
	and #$F0
	sta pu1_vibphase,x
	jmp +here ; skip increment here
	
	+notatend:
	inc pu1_vibphase,x
	+here:
	lda pu1_vibtimeleft,x
	beq +timeover
	dec pu1_vibtimeleft,x ; timer is nonzero so no vibrato applies yet
	rts
	
	+timeover:
	lda temp_6
	asl a
	asl a
	tay
	lda pu1_loSHmu,y
	clc
	adc pu1_vibvalue,x ; add relative sine to the low period
	sta pu1_loSHmu,y
	rts

 Vibrato_TBL: ; contains relative waves for modulation
  .db $01,$01,$00,$01,$00,$00,$FF,$00,$FF,$FE,$FF,$00,$FF,$00,$01,$02  ; 16/60 Hz Depth 3
  .db $02,$01,$00,$FF,$FE,$FE,$FF,$03,$02,$01,$00,$FF,$FE,$FE,$FF,$03 ; 8/60 Hz  Depth 3
  .db $03,$FD,$FD,$03,$03,$FD,$FD,$03,$03,$FD,$FD,$03,$03,$FD,$FD,$03 ; 4/60 Hz  Depth 3
  .db $02,$02,$01,$FE,$FD,$FE,$FF,$03,$02,$02,$01,$FE,$FD,$FE,$FF,$03  ; 8/60 Hz  Depth 5
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Game project help and progress thread

Post by tepples »

za909 wrote:There are really only two options [for defining envelopes] that come to my mind:
1. Implementing some sort of ADSR envelope in software, therefore calculating your volume and other changes with countdowns and selectable load values for these counters (Capcom games use this method)
2. Making a bunch of tables with all the raw values inteded to be consequently written to $4000 and $4004, and then writing the next item one after the other until the stream is terminated or halted, until the engine signals that it has read a note cut byte, which then runs the "release" part of the instrument.
NerdTracker uses mostly 1, and FamiTracker uses all 2. My own music engine uses a mix of the two: tables at 2 bytes per frame for the attack and early decay, and then a simple volume ramp for the late decay and sustain. This technique of data-heavy attacks and parameter-driven sustains was inspired by Linear Arithmetic synthesis, a technique used in Roland's D-50 synthesizer that combines data-heavy PCM attack transients with parameter-driven digital subtractive synthesis for the rest of the note.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

I think I'm going with having streams of volume duty and pitch data that are read every frame. #$00 would be no volume or pitch envelope, so it's skipped. Duty would be assigned to the notes in instrument, but can be changed with the duty stream if necessary. I don't think that I'll be needing values higher than #$0F for the envelopes, so I could use the upper bits to set some parameters, like add or subtract for pitch and loop flag that would instead of changing the values, would jump X amount of bytes back in the stream and read that instead. I attached one of the songs I've made for the game so you get some kind of idea what kind of style I use (I don't use instruments in FamiTracker because for me it's easier to read when volume, pitch, etc are visible).
Kemono Boss 900 BPM.ftm
(13.87 KiB) Downloaded 144 times
EDIT: Here is how I was planning of making the instruments and envelopes:

Code: Select all

InstrumentSQ_Duty0:
 .db %00110000			; Duty 12.5%, Length Counter Halt, Constant Volume

InstrumentSQ_Duty1:
 .db %01110000			; Duty 25%, Length Counter Halt, Constant Volume

InstrumentSQ_Duty2:
 .db %10110000			; Duty 50%, Length Counter Halt, Constant Volume

InstrumentSQ_Duty3:
 .db %11110000			; Duty 75%, Length Counter Halt, Constant Volume

InstrumentNoise:
 .db %00110000

 ; %xxx1 xxxx 0 = Add, 1 = Subtract (Pitch only) ($10)
 ; %xx1x LLLL Loop Flag, Subtract 'LLLL' from envelope stream counter  ($2L)
 ; %x1xx WWWW Wait 'WWWW' frames and move on to the next byte ($4W)
 ; %1xxx xxxx Halt Flag, Stop envelope update and leave last updated value ($80)

				; Square Volume Envelopes

SQVolEnv00:
 .db $07,$07,$07,$06,$06,$05,$80

SQVolEnv01:
 .db $02,$02,$02,$01,$80

SQVolEnv02:
 .db $06,$06,$07,$80

SQVolEnv03:
 .db $02,$02,$02,$00,$00,$01,$80

SQVolEnv04:
 .db $06,$06,$07,$01,$80

SQVolEnv05:
 .db $04,$04,$03,$01,$80

SQVolEnv06:
 .db $02,$02,$03,$01,$80

				; Noise Volume Envelopes

NoiseVolEnv00:
 .db $05,$05,$03,$03,$01,$00,$80

NoiseVolEnv01:
 .db $03,$03,$02,$02,$01,$00,$80

				; Square Pitch Envelopes

SQPtcEnv00:
 .db $43,$14,$00,$07,$00,$17,$00,$24

SQPtcEnv01:
 .db $46,$14,$00,$00,$07,$00,$00,$00,$17,$00,$00,$27

SQPtcEnv02:
 .db $4F,$4B,$14,$00,$00,$07,$00,$00,$00,$17,$00,$00,$27
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

I think I have the data format ready. Do you think that this would be too complex/slow to process, and can it be simplified?

I included only Square 1 channel to not to make the post too long.

Code: Select all

				; Channels in track

MusicBoss:
 .dw BossSQ1,BossSQ2,BossTri,BossNoise,BossDMC

				; Patterns in channel

BossSQ1:
 .dw BossSQ1_00,BossSQ1_00
 .dw BossSQ1_01,BossSQ1_02,BossSQ1_03,BossSQ1_04
 .dw BossSQ1_01,BossSQ1_02,BossSQ1_03,BossSQ1_04
 .dw BossSQ1_05,BossSQ1_06,BossSQ1_05,BossSQ1_07

				; Streams in Pattern

BossSQ1_00:
 .dw BossSQ1_00_Note,BossSQ1_00_Volume,BossSQ1_00_Vibrato
 .dw BossSQ1_00_Duty,BossSQ1_00_Len_Lo,BossSQ1_00_Len_Hi

BossSQ1_01:
 .dw BossSQ1_01_Note,BossSQ1_01_Volume,BossSQ1_01_Vibrato
 .dw BossSQ1_01_Duty,BossSQ1_01_Len_Lo,BossSQ1_01_Len_Hi

BossSQ1_02:
 .dw BossSQ1_02_Note,BossSQ1_02_Volume,BossSQ1_02_Vibrato
 .dw BossSQ1_02_Duty,BossSQ1_02_Len_Lo,BossSQ1_02_Len_Hi

BossSQ1_03:
 .dw BossSQ1_03_Note,BossSQ1_03_Volume,BossSQ1_03_Vibrato
 .dw BossSQ1_03_Duty,BossSQ1_03_Len_Lo,BossSQ1_03_Len_Hi

BossSQ1_04:
 .dw BossSQ1_04_Note,BossSQ1_04_Volume,BossSQ1_04_Vibrato
 .dw BossSQ1_04_Duty,BossSQ1_04_Len_Lo,BossSQ1_04_Len_Hi

BossSQ1_05:
 .dw BossSQ1_05_Note,BossSQ1_05_Volume,BossSQ1_05_Vibrato
 .dw BossSQ1_05_Duty,BossSQ1_05_Len_Lo,BossSQ1_05_Len_Hi

BossSQ1_06:
 .dw BossSQ1_06_Note,BossSQ1_06_Volume,BossSQ1_06_Vibrato
 .dw BossSQ1_06_Duty,BossSQ1_06_Len_Lo,BossSQ1_06_Len_Hi

BossSQ1_07:
 .dw BossSQ1_07_Note,BossSQ1_07_Volume,BossSQ1_07_Vibrato
 .dw BossSQ1_07_Duty,BossSQ1_07_Len_Lo,BossSQ1_07_Len_Hi
And here is example of one pattern's streams:

Code: Select all

BossSQ1_00_Note:
 .db B_2,As2,A_2,Gs2            ; Notes represent a value from range of $00 - $5E
 .db B_2,As2,A_2,Gs2            ; This value is used in X or Y incremented addressing
 .db B_2,As2,A_2,Gs2            ; to get the correct period value for the note
 .db B_2,As2,A_2,Gs2

BossSQ1_00_Volume:
 .db SQVolEnv00,SQVolEnv00,SQVolEnv00,SQVolEnv00           ; Volume, pitch and duty envelopes are in my previous post
 .db SQVolEnv00,SQVolEnv00,SQVolEnv00,SQVolEnv00
 .db SQVolEnv00,SQVolEnv00,SQVolEnv00,SQVolEnv00
 .db SQVolEnv00,SQVolEnv00,SQVolEnv00,SQVolEnv00

BossSQ1_00_Vibrato:
 .db SQPtcEnv00,SQPtcEnv00,SQPtcEnv00,SQPtcEnv00
 .db SQPtcEnv00,SQPtcEnv00,SQPtcEnv00,SQPtcEnv00
 .db SQPtcEnv00,SQPtcEnv00,SQPtcEnv00,SQPtcEnv00
 .db SQPtcEnv00,SQPtcEnv00,SQPtcEnv00,SQPtcEnv00

BossSQ1_00_Duty:
 .db SQ_Duty1,SQ_Duty1,SQ_Duty1,SQ_Duty1
 .db SQ_Duty1,SQ_Duty1,SQ_Duty1,SQ_Duty1
 .db SQ_Duty1,SQ_Duty1,SQ_Duty1,SQ_Duty1
 .db SQ_Duty1,SQ_Duty1,SQ_Duty1,SQ_Duty1

BossSQ1_00_Len_Lo:
 .db $06,$07,$06,$07
 .db $06,$07,$06,$07
 .db $06,$07,$06,$07
 .db $06,$07,$06,$07

BossSQ1_00_Len_Hi:
 .db $00,$00,$00,$00                 ; I have these just in case
 .db $00,$00,$00,$00                 ; Not sure if I'll ever need these
 .db $00,$00,$00,$00
 .db $00,$00,$00,$00

BossSQ1_01_Note:
 .db B_2,Cs3,D_3,Fs3,E_3,D_3,A_2

BossSQ1_01_Volume:
 .db SQVolEnv02,SQVolEnv02,SQVolEnv02,SQVolEnv02,SQVolEnv02,SQVolEnv02,SQVolEnv02

BossSQ1_01_Vibrato:
 .db SQPtcEnv00,SQPtcEnv00,SQPtcEnv01,SQPtcEnv01,SQPtcEnv01,SQPtcEnv01,SQPtcEnv01

BossSQ1_01_Duty:
 .db SQ_Duty1,SQ_Duty1,SQ_Duty1,SQ_Duty1,SQ_Duty1,SQ_Duty1,SQ_Duty1

BossSQ1_01_Len_Lo:
 .db $06,$07,$0D,$13,$14,$1A,$0C

BossSQ1_01_Len_Hi:
 .db $00,$00,$00,$00,$00,$00,$00

BossSQ1_02_Note:
 .db B_2

BossSQ1_02_Volume:
 .db SQVolEnv02

BossSQ1_02_Vibrato:
 .db SQPtcEnv02

BossSQ1_02_Duty:
 .db SQ_Duty1

BossSQ1_02_Len_Lo:
 .db $68

BossSQ1_02_Len_Hi:
 .db $00
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
za909
Posts: 249
Joined: Fri Jan 24, 2014 9:05 am
Location: Mijn hart woont al in Nederland

Re: Game project help and progress thread

Post by za909 »

You're on the right track but by having looked at your plans, I can't help advising you to find some other way of representing your note effects because this produces TONS of redundant data, for example you are storing the current instrument for every single note. Instead, you should keep a variable like "Pu1Instr" or something, use that as an index to find the table representing this instrument number, and having an effect command to change the Pu1Instr variable. This way you will only have two bytes added when you actually need to switch to different instrument.
I remember in my earlier sound engine I did this by setting up an indirect vector to the desired table, then loading the current volume write number into Y and reading from the table. (Sorry, couldn't find my source code for that)
As you can see I only had 16 pulse instruments, the other bits of p1_flags were bitflags for detune, hw. sweep mode, etc.

Code: Select all

  GetInstrumentId:
; Put instrument ID in Y for index

	lda p1_flags,x 
	and #%00001111
	tay	
	rts

Code: Select all

; Y is Instrument ID
   sty temp_4
	tya
	asl a
	tay
	lda instrumentaddrTBL,y
	sta temp_E
	lda instrumentaddrTBL+1,y
	sta temp_F
	ldy p1_patchseq,x
	lda (temp_E),y
	sta p1_shvol,x
	ldy temp_4
	rts
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

za909 wrote:Instead, you should keep a variable like "Pu1Instr" or something, use that as an index to find the table representing this instrument number, and having an effect command to change the Pu1Instr variable. This way you will only have two bytes added when you actually need to switch to different instrument.
That actually sounds better. I'll have to edit the data later today.

By the way, when I start to actually write the sound engine, which one of these would be better:
1. Run the whole sound engine in NMI
2. Run sound engine outside NMI and copy bytes to the registers during NMI

I have heard both of these methods, but I'm not sure which one is better and what are their possible downsides.
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

Tsutarja wrote:2. Run sound engine outside NMI and copy bytes to the registers during NMI
This approach makes more sense for PPU updates, since access to VRAM is restricted, but the APU can be accessed at any time, so there's no problem in doing everything at once.

The only advantage I can see in buffering APU updates is that it might make the timing a little steadier, since the time taken to process the audio data won't delay the register writes, but unless your sound engine's execution times vary greatly, I doubt the difference is noticeable.
User avatar
za909
Posts: 249
Joined: Fri Jan 24, 2014 9:05 am
Location: Mijn hart woont al in Nederland

Re: Game project help and progress thread

Post by za909 »

By the way, if you are struggling with DPCM samples and the space loss they cause in the fixed bank, you can also try playing your code as a sound! Most of the time it's just random garbage (but you can still use it as a looped wind sound so your noise channel is free to do something else) but I found that an unrolled PLA STA $2007 loop conveniently produces a C note sound with a very wavetable-esque tone to it. I made a little happy loop with it in Famitracker to show what it sounds like but my engine could do it as well if I spent an hour or two with it. (If you are working with CHR-RAM you can also show animated tiles made of code, sometimes you can find a useful flashing tile or something like that)

Edit: As far as I know, Rockman 4 MI uses this method and plays some garbled sounds together with the noise channel.
Attachments
nmisampletest.ftm
(4.61 KiB) Downloaded 133 times
Post Reply