SNES Programing Help 2

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
SNES Programing Help 2
by on (#142474)
I don't know if I should have just resurrected the old thread, but I figured I would ask more "advanced" questions this time. I was wondering how everyone makes an object go to different pieces of code that correspond to a different action, like if the object is jumping or if it is on the ground. I suppose you could use a jump table, but I guess there isn't really a "definitive" way to do these kind of things. I wrote a code where the player spawns a bullet, and when I spawned the bullet, I wrote a certain number to a register in the bullets object slot. When the bullet code gets jumped to, I load the special register and I offset a jump table by the register which jumps to a certain piece of code that works for the direction of the bullet. I was thinking I could just change the bullet's velocity and check all sides of the screen instead of just the one the bullet is traveling, but the bullet's graphics would be the same and so would everything else including the hit box, so I really don't know.

If it helps, here is the code:

Code:
;====================================================================================
;Bullet
;====================================================================================

.proc bullet
  rep #$30                         ; A=16, X/Y=16
  ldx ObjectTable+6,y
  jmp (BulletIdentificationTable,x)   ;jump to the code that corresponds with the object

.endproc

;====================================================================================

BulletIdentificationTable:
  .word bulletright,bulletleft

;====================================================================================

.proc bulletright
  lda ObjectTable+2,y
  cmp #256
  bcs terminate_bullet
  clc
  adc #$0A
  sta ObjectTable+2,y


  lda Bullet1MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount              ; ...and store it in MetaspriteCount

  ldx #Bullet1MetaspriteTable      ; Offset into MetaspriteTable
  stx MetaspriteTableOffset

  lda ObjectTable+2,y
  sta MetaspriteXPosition
  lda ObjectTable+4,y
  sta MetaspriteYPosition

  stz MetaspriteDirection

  jsr start_metasprite      ; jump to start_metasprite to build metasprites
  rts

terminate_bullet:
  lda #$0000
  sta ObjectTable,y
  rts

.endproc

;====================================================================================

.proc bulletleft
  lda ObjectTable+2,y
  cmp #256
  bcs terminate_bullet
  sec
  sbc #$0A
  sta ObjectTable+2,y


  lda Bullet1MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount              ; ...and store it in MetaspriteCount

  ldx #Bullet1MetaspriteTable      ; Offset into MetaspriteTable
  stx MetaspriteTableOffset

  lda ObjectTable+2,y
  sta MetaspriteXPosition
  lda ObjectTable+4,y
  sta MetaspriteYPosition

  lda #$0001
  sta MetaspriteDirection

  jsr start_metasprite      ; jump to start_metasprite to build metasprites
  rts

terminate_bullet:
  lda #$0000
  sta ObjectTable,y
  rts

.endproc
Re: SNES Programing Help 2
by on (#142477)
Quote:
Code:
  lda ObjectTable+2,y
  sta MetaspriteXPosition
  lda ObjectTable+4,y
  sta MetaspriteYPosition

  stz MetaspriteDirection

  jsr start_metasprite      ; jump to start_metasprite to build metasprites

Why not just have start_metasprite reference the X and Y positions in the object variables directly, instead of copying? Unless you plan to use start_metasprite outside of your object loop...

Regarding how you set this section up, I think the use of a jump table for left and right is a bit convoluted when you can just test something and branch. You don't even need to duplicate this section at all if you store their velocity in a variable and set it when they're initialized, then just add that every time.

Also, are you using direct page? I guess if your object list is anywhere other than the first $2000 of RAM you can't (err, can you use direct page in bank 7F to access another $2000 of RAM? Haven't tried using it outside bank 0 yet), but it would speed things up on all the indexed-by-y instructions. I'm not sure if you're using it some other way, it's just an idea if you haven't messed with direct page yet. Better to get it set up now before you go and write several thousand lines of code you'll need to change later.

Others are free to correct me because I am new.
Re: SNES Programing Help 2
by on (#142480)
Khaz wrote:
Why not just have start_metasprite reference the X and Y positions in the object variables directly, instead of copying? Unless you plan to use start_metasprite outside of your object loop...

X and Y are both already being used in the metasprite routine, that is why. I don't plan on jumping to the routine outside of it.

Khaz wrote:
Regarding how you set this section up, I think the use of a jump table for left and right is a bit convoluted when you can just test something and branch.

I plan on adding a bunch of different angles, so I figured I'd just make a jump table so I wouldn't have to have a bunch of cmp,beq's.

Khaz wrote:
You don't even need to duplicate this section at all if you store their velocity in a variable and set it when they're initialized, then just add that every time.

How would "negative velocity" (up or right) work? Also, you would need to have it to where the object is flipped around still, so you couldn't just add the velocity to the object position.

A little random for me to ask this, but is there some sort of instruction that can "flip" bits around? I mean like if you had 11111010 and you wrote 00000011 then the result would be 11111001.
Re: SNES Programing Help 2
by on (#142481)
Espozo wrote:
X and Y are both already being used in the metasprite routine, that is why. I don't plan on jumping to the routine outside of it.

In that case, that's the beautiful part of my earlier suggestion of using direct page. Once you get that set up, you can just have metasprite routine call the X and Y positions from the current direct page, without having to touch your X and Y registers.
Espozo wrote:
I plan on adding a bunch of different angles, so I figured I'd just make a jump table so I wouldn't have to have a bunch of cmp,beq's.

If they're all still travelling in straight lines, you can still have one generic update routine that adds your X and Y velocities to your positions for all of them. If you plan to have them all behave significantly differently then your approach starts to make sense.
Espozo wrote:
How would "negative velocity" (up or right) work? Also, you would need to have it to where the object is flipped around still, so you couldn't just add the velocity to the object position.

A little random for me to ask this, but is there some sort of instruction that can "flip" bits around? I mean like if you had 11111010 and you wrote 00000011 then the result would be 11111001.

Negative binary numbers in general all work the same way: $0000 rolls over into $FFFF, which is -1. So if your velocity is negative $10 then it's just $FFF0. The highest bit determines whether the number is positive or negative. To add that is the same whether it's positive or negative: clc, adc.

And yes, there is a flip bits instruction: EOR (Exclusive OR). This is also useful with negative numbers: To flip the sign on a number, you EOR it with $FFFF (flip all bits), then add one (because you have to add one after flipping all the bits to make it equal because math. Just remember: If you do $0000 EOR $FFFF, you get $FFFF so you have to add one to make it zero again.).

EDIT: Also, you meant to say "negative velocity (up or LEFT)", right?
Re: SNES Programing Help 2
by on (#142482)
Khaz wrote:
(err, can you use direct page in bank 7F to access another $2000 of RAM? Haven't tried using it outside bank 0 yet)

Yes. Direct page always accesses bank 0, regardless of the value of the data bank register.
Re: SNES Programing Help 2
by on (#142483)
Khaz wrote:
Also, are you using direct page? I guess if your object list is anywhere other than the first $2000 of RAM you can't (err, can you use direct page in bank 7F to access another $2000 of RAM? Haven't tried using it outside bank 0 yet) ...

Direct page is hard-coded to bank $00.

Direct page is, in essence, the exact same as 6502/65c02 zero page: instructions are 2 bytes long (e.g. lda $12), with a range of 256 bytes ($00-FF). The difference is that instead of the effective address being $0000 to $00FF like with zero page, the D register (e.g. lda #$1000 / tcd) allows you to relocate the "base offset" for direct page reads/writes so you're no longer limited to $00xx.

If you need to be able to access memory in other banks, you need to either use absolute addressing and change B around dynamically (lda #$7f / pha / plb / lda $1000) or use long addressing (lda $7f1000). There's also absolute long indexed X (lda $7f1000,x) and direct page indirect long indexed Y (e.g. lda [$12],y), but I won't go into those here because it's outside of scope.

Equally important: the SNES has MMIO registers $2180 (WMDATA) and $2181 through $2183 (WMADDL/WMADDM/WMADDH) that allows a way to access to WRAM (memory $7E0000 to $7FFFFF). You might wonder (like I did at one point) what the purpose of those registers is if you can already access the same memory natively: the answer is general-purpose DMA (since it can read/write from an MMIO register, which makes this the fastest way to move large amounts of memory in/out of WRAM).
Re: SNES Programing Help 2
by on (#142484)
Khaz wrote:
Espozo wrote:
I plan on adding a bunch of different angles, so I figured I'd just make a jump table so I wouldn't have to have a bunch of cmp,beq's.

If they're all still travelling in straight lines, you can still have one generic update routine that adds your X and Y velocities to your positions for all of them. If you plan to have them all behave significantly differently then your approach starts to make sense.

The bullet isn't circular though, so velocity isn't the only thing changing.

Khaz wrote:
Negative binary numbers in general all work the same way: $0000 rolls over into $FFFF, which is -1. So if your velocity is negative $10 then it's just $FFF0. The highest bit determines whether the number is positive or negative. To add that is the same whether it's positive or negative: clc, adc.

But how can it tell if it is -16 or if it is 65528? (I think the second number is correct)

Khaz wrote:
And yes, there is a flip bits instruction: EOR (Exclusive OR). This is also useful with negative numbers: To flip the sign on a number, you EOR it with $FFFF (flip all bits), then add one (because you have to add one after flipping all the bits to make it equal because math. Just remember: If you do $0000 EOR $FFFF, you get $FFFF so you have to add one to make it zero again.).

Thank you! :) I've known of eor's existence but I had now idea as to what it did.

Khaz wrote:
EDIT: Also, you meant to say "negative velocity (up or LEFT)", right?

Yeah. :oops: (I'm really not feeling it today.)

93143 wrote:
Khaz wrote:(err, can you use direct page in bank 7F to access another $2000 of RAM? Haven't tried using it outside bank 0 yet)Yes. Direct page always accesses bank 0, regardless of the value of the data bank register.

Is there a good place to learn all this "direct page" stuff? I've never really understood or ever really even attempted to understand it. I've always wondered, how are you loading a 24bit address?
Re: SNES Programing Help 2
by on (#142485)
Espozo wrote:
The bullet isn't circular though, so velocity isn't the only thing changing.

What else is changing?
Espozo wrote:
But how can it tell if it is -16 or if it is 65528? (I think the second number is correct)

It is both. Just try adding both of those to a two byte number and you'll get the same result.
Espozo wrote:
Is there a good place to learn all this "direct page" stuff?

There really doesn't seem to be, you just kind of have to start trying to use it and once it works you'll get it. The idea is that you set your D register to a base address, then when you perform instructions with a one-byte-address operand, say "LDA $10", it will automatically add that value in D to the address, and quickly.

EDIT: Regarding "what's changing"... For the different sprites, simply load the index of the metasprite into the object's internal variables when it spawns, and have your metasprite routine draw whatever is in that variable every time.
Re: SNES Programing Help 2
by on (#142488)
Khaz wrote:
Espozo wrote:But how can it tell if it is -16 or if it is 65528? (I think the second number is correct)It is both. Just try adding both of those to a two byte number and you'll get the same result.

Oh yeah, because it wraps around... (Today's really not my day. :oops: )

Khaz wrote:
Espozo wrote:
The bullet isn't circular though, so velocity isn't the only thing changing.

What else is changing?

The sprite tile number (changes based on where the bullet tile data got loaded into vram) and the tile data that gets uploaded to vram for it, (yet to be implemented, as I currently just have all the bullet tile data at a fixed spot in vram that gets loaded from a different code) and the direction, which is a register that the metasprite code flips the entire metasprite around if it is set, meaning it is not 0.

Khaz wrote:
EDIT: Regarding "what's changing"... For the different sprites, simply load the index of the metasprite into the object's internal variables when it spawns, and have your metasprite routine draw whatever is in that variable every time.

:| ? What do you mean?
Re: SNES Programing Help 2
by on (#142491)
What I mean is, store the address of the metasprite table as a variable inside the object. Every frame, have your build metasprite routine look up the metasprite table at that address and draw it. This way you don't have to decide every frame which metasprite to draw. You just update that address when you want it to change, and it will happen automatically.

Even if the bullets are animated, you could still arrange the metasprite tables sequentially for each animation and have your framely routine just increment the "metasprite table address" variable by the size of each table in a set pattern. This way you can have completely unique animations running in all different directions using the exact same framely-update routine.
Re: SNES Programing Help 2
by on (#142552)
Okay, this is a small thing, but I just started trying to implement the velocity thing and I wrote "adc #-$0A" and ca65 is telling me

Quote:
Objects.asm(290): Error: Range error (-10 not in [0..65535])

Does it not understand that it wraps around? Do I have to figure the number out manually?
Re: SNES Programing Help 2
by on (#142553)
At some point recently, the previous ca65 maintainer decided that there was "no right way" to DTRT with mixing signed and unsigned integers. So as a result you have to forcefully cast it to 8 bits.

This is bunk, but we haven't managed(/tried?) to persuade the new maintainer to fix it.
Re: SNES Programing Help 2
by on (#142556)
Espozo wrote:
Do I have to figure the number out manually?

Would this work?
Code:
adc #$100-$0A (8-bit)
adc #$10000-$0A (16-bit)
Re: SNES Programing Help 2
by on (#142560)
Espozo wrote:
Okay, this is a small thing, but I just started trying to implement the velocity thing and I wrote "adc #-$0A" and ca65 is telling me

Quote:
Objects.asm(290): Error: Range error (-10 not in [0..65535])

Does it not understand that it wraps around? Do I have to figure the number out manually?


The ca65 maintainer removed that functionality a while ago, apparently its to force you to think when coding 16 bit signed math on a 8 bit 6502.

Instead you can use one of 5 functions to preform casts to 8 or 16 bit values.

  • .loword - bits 0 - 15 (lowest 16 bits) of the argument.
  • .hiword - bits 16 - 31 of the argument.
  • .lobyte - bits 0 - 7 (lowest 8 bits) of the argument
  • .hibyte - bits 8 - 15 (second byte) of the argument.
  • .bankbyte - bits 16 - 23 (third byte) of the argument.



For example.

16 bit values:
Code:
    LDA #.loword(-10)


And 8 bit values:
Code:
    LDA #.lobyte(-10)


My code is full of them.
Re: SNES Programing Help 2
by on (#142567)
lidnariq wrote:
At some point recently, the previous ca65 maintainer decided that there was "no right way" to DTRT with mixing signed and unsigned integers. So as a result you have to forcefully cast it to 8 bits.

This is bunk, but we haven't managed(/tried?) to persuade the new maintainer to fix it.

The official explanation was that it was a bug/oversight, and was never supposed to work like that in the first place.

No need to persuade anybody though, you can use...
Code:
.feature force_range

...to disable all range checks. (There's also an --feature command line option that does the same thing.)
http://cc65.github.io/doc/ca65.html#ss11.42

For people who say that ca65 should be able to "DTRT" for signed and unsigned integers, do you know of any other assembler that actually treats them separately?
Re: SNES Programing Help 2
by on (#142571)
Heh. I'm quite content doing literally everything in Hex. It's only taken a couple months or so for it to become second nature to me, and that way there's never any misunderstanding about what my code is doing. In most cases it's even easier than thinking in decimal: A standard tile is $10 or 16, so say 17 of them would be $170 or (let me get a calculator).

Speaking of you can always use the windows calculator in "Programmer" mode to do conversions and math in hex/binary that you can't do in your head (If you use Windows).
Re: SNES Programing Help 2
by on (#142645)
You know, I've been a bit busy with school so I haven't been able to work on anything, but I should have some free time and I wanted to implement the velocity thing for the bullets, but I just realized something. I was originally going to use a word for velocity and a word for the position, and I was going to only take the highest byte from the velocity and add that to the position, as this would allow for "sub pixel precision", but I later realized that I wouldn't be able to have "negative" velocity, because I couldn't get a 16 space to wrap around using an 8 bit number. Do you have any good ideas as to have both "sub pixel velocity" and "negative velocity"?
Re: SNES Programing Help 2
by on (#142647)
If you ignore the low byte and add only the high byte to the velocity, then you can't have motion slower than 1 pixel per frame. There is a workaround involving adding a bit-reversed version of the current frame number to the low byte of the velocity and then adding the carry from there and the high byte of the velocity to the displacement.

I don't see how a two's complement negative velocity would introduce problems. What problems have you seen?
Re: SNES Programing Help 2
by on (#142649)
The way I've been doing subpixel negative values is by doing this:

Code:
lda {x_velocity}     //if greater than $8000, N flag is set
bpl +                        //branch if plus
dec {x_position_hi}     //decrease high-word of x position if velocity is negative
+;
Re: SNES Programing Help 2
by on (#142653)
In my setup velocity is two bytes where the hi byte represents actual pixels/frame, and position is 3 bytes where the middle represents actual pixels. I branch based on whether velocity is positive or negative, add the sub-velocity to the sub-position in 16-bit mode, then bcc or bcs appropriately to increment or decrement the hi-byte of position.

I'm sure there's some better way...
Re: SNES Programing Help 2
by on (#142673)
That's what the carry/borrow part of ADC/SBC is for, to let you easily carry/borrow lower portions of a multi-byte(/multi-word, for SNES) addition/subtraction.
Re: SNES Programing Help 2
by on (#142674)
Myask wrote:
That's what the carry/borrow part of ADC/SBC is for, to let you easily carry/borrow lower portions of a multi-byte(/multi-word, for SNES) addition/subtraction.

I'm not sure if this was being addressed to my comment but if so: Do you mean to say I'm doing things as intended or are you proposing a faster way? I've set it up like this:
Code:
   lda.b objYVelo   ;16-bit A initially
   beq _doneObjYVelo
   bpl _goinDown
   
   clc               ;goin' Up
   adc.b objYPosnSub
   sta.b objYPosnSub   ;Velocity was NEGATIVE. 
   bcs _doneObjYVelo   ;So if carry is CLEAR, we need to DECREMENT high byte

   sep #$20      ;8-bit A
   dec.b objYPosnHi
   rep #$20      ;16-bit A
   bra _doneObjYVelo

_goinDown:
   clc
   adc.b objYPosnSub
   sta.b objYPosnSub   ;Velocity was POSITIVE. 
   bcc _doneObjYVelo   ;If carry is SET, we need to INCREMENT high byte

   sep #$20      ;8-bit A
   inc.b objYPosnHi
   rep #$20      ;16-bit A

_doneObjYVelo:

Which, aside from being extra-cautious about overflows on the high byte, is as efficient as I can think of a way to do it. I know you can use an ADC #$00 on the Hi byte to stuff the carry in there without a branch, at least on the second half, but that seems like it costs more cycles. Am I missing something or have I got it right?

(P.S. I instinctively put the "beq _doneObjYVelo" at the start to eliminate all the processing when your velocity is zero. I do that a lot but I suddenly think that's a mistake - since it will speed up processing some of the time but ultimately make the worst-case scenario take slightly longer. I should refrain from doing that, right?)
Re: SNES Programming Help 2
by on (#142675)
It was, but...
Quote:
I know you can use an ADC #$00 on the Hi byte to stuff the carry in there without a branch, at least on the second half, but that seems like it costs more cycles.

Code:
LDA imm: 2b 2c
ADC/SBC mem: 2b, 3c
STA mem: 2b, 3c
total: 8b, 10c
vs
BC*: 2b, 2c (3c not, but ditches the SEP/REP too for net -3c)
INC/DEC mem: 2b, 5c
total: 5b, 8c
You're right. Silly of me to think that the obvious method would be cheaper. (if indexed/not direct page, the gap just widens, as the ADC method has two instructions gaining cycles rather than one.)

Small optimization: put the CLC before the LDA.b objYVelo, rather than in both branches. LDA doesn't affect carry.

Quote:
I instinctively put the "beq _doneObjYVelo" at the start [...] I should refrain from doing that, right?
Probably unnecessary/unuseful. It depends on what your 'worst-case' where you're actually needing time-saved looks like. Untaken branch takes 2 cycles each time, after all, so when is 2*moving_objects < (processing_time -3) * unmoving_objects?

...say, why AREN'T you using indexed modes here? Aren't you iterating over the list of objects?
Re: SNES Programing Help 2
by on (#142676)
To avoid repetitive index register juggling.
Re: SNES Programing Help 2
by on (#142677)
Yeah, I'm using direct page to iterate through my list of objects, hence all the ".b" instructions.

Thanks for the input!
Re: SNES Programing Help 2
by on (#142685)
Sorry to steer this conversation off topic a little, but there's one thing I've been doing that I'm sure there's a better way to do. I have it to when whenever I set a certain bit to the metasprite routine, it creates a horizontally flipped version of the metasprite, but I wasn't sure how to efficiently make one routine be able to do both, so I literally did this...

Code:
  lda MetaspriteDirection
  bne backwards_metasprite_loop

Which means I completely copied the code again except I subtracted the sprites position from the objects instead of adding and I eor'd at one point to change the flip bit. I know there's a better way to do this, and at some point, I want to also have sprites flipped vertically, and that means I'd have to copy the routine 4 times... I know I could look to see if the bit was set to change direction every time I do something that related to the object being flipped, but this would use some more processing time so don't know...
Re: SNES Programing Help 2
by on (#142689)
Espozo wrote:
Sorry to steer this conversation off topic a little...

Helping you is literally the topic. :wink:
Espozo wrote:
I know there's a better way to do this, and at some point, I want to also have sprites flipped vertically, and that means I'd have to copy the routine 4 times... I know I could look to see if the bit was set to change direction every time I do something that related to the object being flipped, but this would use some more processing time so don't know...

I personally found psycopathicteen's reply to you back in December very helpful for that. It needs a bit of adjustment to be functional (position is off-by-one on flipping and I have no clue what his plan was with the x-position and size bits for the second OAM table), but I found the approach downright elegant.
Re: SNES Programing Help 2
by on (#142693)
Khaz wrote:
Espozo wrote:
Sorry to steer this conversation off topic a little...

Helping you is literally the topic. :wink:

Yeah... :oops: I just figured I was moving on from the velocity thing for now.

Khaz wrote:
Espozo wrote:
I know there's a better way to do this, and at some point, I want to also have sprites flipped vertically, and that means I'd have to copy the routine 4 times... I know I could look to see if the bit was set to change direction every time I do something that related to the object being flipped, but this would use some more processing time so don't know...

I personally found psycopathicteen's reply to you back in December very helpful for that. It needs a bit of adjustment to be functional (position is off-by-one on flipping and I have no clue what his plan was with the x-position and size bits for the second OAM table), but I found the approach downright elegant.

Oh, thanks for finding that again. :) I didn't have the slightest clue as to what was going on originally, but now that I've learned a bit since then, I kind of understand it now. Maybe psychopathicteen can explain it some more here.
Re: SNES Programing Help 2
by on (#142694)
Try to avoid copying code. That's when it's best to use a macro, with parameters for the parts that vary. Same result, but it's always good to edit the code in one place instead of 4.

If you wanted to avoid subtraction, instead of SBC #8 you could do ADC #$F8 (or $FFF8) for the same result. Though I know that doesn't help much, that just moves the branch elsewhere, figured I'd throw that in as well.
Re: SNES Programing Help 2
by on (#142695)
Memblers wrote:
If you wanted to avoid subtraction, instead of SBC #8 you could do ADC #$F8 (or $FFF8) for the same result. Though I know that doesn't help much, that just moves the branch elsewhere, figured I'd throw that in as well.

I know. (Even if I did just learn recently...) The thing is that the number that is being sbc'd/adc'd is the same, so I cannot really change it. Hey, maybe at the beginning of the code, I could check if the object is backwards, and if it is, I then add #$F000 to the position of the sprite position that gets added to the metasprite position... To make sure it to where it loads either #$0000 or #$F000, I'll make it look at a register that will only ever have those 2 values. (This is the result of if the object is backwards or forwards that gets filled at the beginning of the code.)

By the way, what is the opcode "bit" do?
Re: SNES Programing Help 2
by on (#142698)
Espozo wrote:
By the way, what is the opcode "bit" do?

It's the same as and, only it doesn't change the accumulator; it just sets the status flags.

EDIT: Not quite; see below. That's what I get for trying to impart knowledge I haven't used myself yet...
Re: SNES Programing Help 2
by on (#142701)
93143 wrote:
status flags.

What? Sorry... :oops:
Re: SNES Programing Help 2
by on (#142705)
In the processor status register.

Eyes and Lichty (1992) wrote:
BIT sets the P status register flags based on the result of two different operations, making it a dual-purpose instruction:

First, it sets or clears the n flag to reflect the value of the high bit of the data located at the effective address specified by the operand, and sets or clears the v flag to reflect the contents of the next-to-highest bit of the data addressed.

Second, it logically ANDs the data located at the effective address with the contents of the accumulator; it changes neither value, but sets the z flag if the result is zero, or clears it if the result is non-zero.

[...] When the BIT instruction is used with the immediate addressing mode, the n and v flags are unaffected.

So it's not exactly the same as and...
Re: SNES Programing Help 2
by on (#142757)
Huh, I forgot about the effect that BIT had on the N and V flags. Sounds handy, you can test three conditions with a single instruction...

... except that it's setting Z based on the accumulator, but V and N based on the value at the operand address, which makes it confusing and weird to me... I can see it being handy if it worked in immediate mode, such that every BIT instruction automatically tests the highest two bits of the accumulator and stores them in the V and N flags so you effectively get three BITs in one, but I can't really understand the use of its actual function.
Re: SNES Programing Help 2
by on (#142760)
Think of it in reverse: the mask is in A and the address contains the value you want to test (the zero flag is set based on the result after the AND so it's technically neither operand).
Re: SNES Programing Help 2
by on (#142804)
93143 wrote:
In the processor status register.
[...] When the BIT instruction is used with the immediate addressing mode, the n and v flags are unaffected.

??? Is this specific to the 65816, or the C/CS models as well? Because it's not like that for the 6280. The immediate mode sets the same n/v flags as with the other addressing modes. Then again, it has TST #$nn , EA instruction so maybe the logic is redundant/shared (neither write to Acc).
Re: SNES Programing Help 2
by on (#142806)
That was a direct quote from "Programming the 65816" from WDC, and from context it seems to apply to the 65C02 and 65802 as well (6502 doesn't have immediate mode for this opcode). I haven't had occasion to try it, but that's what the man said.
Re: SNES Programing Help 2
by on (#142809)
Sorry about this, but I was going to hold off this question so I didn't sound stupid, but I don't care. The bit instruction can be used as an "and" and a "cmp", so you only use one instruction instead of two? It seems like that's what's happening in psychopathicteen's code.
Re: SNES Programing Help 2
by on (#142812)
Espozo wrote:
Sorry about this, but I was going to hold off this question so I didn't sound stupid, but I don't care. The bit instruction can be used as an "and" and a "cmp", so you only use one instruction instead of two? It seems like that's what's happening in psychopathicteen's code.



I didn't look at his code, but just think about what the instruction does for a sec. CMP tests values, BIT tests 'bits'. You can test one bit, or more than one bit. You don't need a compare instruction, because you're only testing if 1 or more bits are zero or not zero. And those bits don't need to be next to each other either, etc. You can do this without BIT, but you would have to AND with Acc first, which means you've destroyed the original contents of Acc. A byte (or word) might have both data and flags in the value. This is one way to test and branch as needed. Or a byte/word might just be a series of state flags, and you test them in a sequential fashion or whatever (i.e. you don't want to keep loading isolating bits to test with AND).

The N/V flags as a different aspect of it though. You can use the BIT instruction to basically test these two bits of a byte in memory, without loading anything into a register. It saves some cycles. If you use an indexing mode, you can exploit this further (you have fast/instance access to whether the data/struct of an array is valid/applicable or not, using an auxiliary list/array/table/etc).

93143: IIRC, there are technically two versions of the 65C02 from WDC. One is the original processor, and the other is the 65C02 MCU they later developed. The later MCU is supposed to have taken the new instructions that Rockwell added to their own R65C02S (sometimes listed as 65CS02, which is the branch the 6280 is from).
Re: SNES Programing Help 2
by on (#142813)
Espozo wrote:
Sorry about this, but I was going to hold off this question so I didn't sound stupid, but I don't care. The bit instruction can be used as an "and" and a "cmp", so you only use one instruction instead of two? It seems like that's what's happening in psychopathicteen's code.

No -- unlike and, bit DOES NOT modify the accumulator. 93143 covered how it works by quoting the manual: viewtopic.php?p=142705#p142705

It's easy to skim over/miss the key point there (in the last line). Rephrased: the "Flags Affected" section is the most useful part because it becomes very clear what addressing mode used affects what flags. So here it is from the manual. To anyone who doesn't understand the opcode: PLEASE READ THIS SECTION SLOWLY. DO NOT SKIM.

Code:
Test Memory Bits against Accumulator

   BIT sets the P status register flags based on the result of two different operations,
making it a dual-purpose instruction:

   First, it sets or clears the n flag to reflect the value of the high bit of the data
located at the effective address specified by the operand, and sets or clears the v
flag to reflect the contents of the next-to-highest bit of the data addressed.

   Second, it logically ANDs the data located at the effective address with the con-
tents of the accumulator; it changes neither value, but sets the z flag if the result is
zero, or clears it if the result is non-zero.

  BIT is usually used immediately preceding a conditional branch instruction: to
test a memory value’s highest or next-to-highest bits; with a mask in the accumula-
tor, to test any bits of the memory operand; or with a constant as the mask (using
immediate addressing) or a mask in memory, to test any bits in the accumulator.
All of these tests are non-destructive of the data in the accumulator or in memory.
When the BIT instruction is used with the immediate addressing mode, the n and v
flags are unaffected.

   8-bit accumulator/memory (all processors): Data in memory is eight-bit; bit 7 is
moved into the n flag; bit 6 is moved into the v flag.

   16-bit accumulator/memory (65802/65816 only, m = 0): Data in memory is
sixteen-bit: the low-order eight bits are located at the effective address; the high-
order eight bits are located at the effective address plus one. Bit 15 is moved into
the n flag; bit 14 is moved into the v flag.

Flags Affected: n v - - - - z - (Other than immediate addressing)
                - - - - - - z - (Immediate addressing only)
                  n   Takes value of most significant bit of memory data.
                  v   Takes value of next-to-highest bit of memory data.
                  z   Set if logical AND of memory and accumulator is zero; else cleared.

Addressing modes supported by BIT:

  • Immediate (opcode $89) -- BIT #$22
  • Absolute (opcode $2C)--. BIT $1234
  • Direct Page (DP) (opcode $24) -- BIT $16
  • Absolute Indexed,X (opcode $3C) -- BIT $1234,X
  • DP Indexed,X (opcode $34) -- BIT $16,X
Re: SNES Programing Help 2
by on (#142830)
Oh wait, I just looked through the comments and I noticed something...

Khaz wrote:
I have no clue what his plan was with the x-position and size bits for the second OAM table


That is what is "unofficially" called the "Soft OBC-1" technique. Because oam on the SNES is a pain in that there is a second sprite table for the 9th x bit and the sprite size bit, (see the relation?) psychopathicteen (unless he isn't actually the one) thought of the idea of creating a second sprite table that is just like the actual second sprite table in oam, except that this one is 512 bytes just like the first sprite table, so when you increment the pointer for the first sprite table, you are also incrementing the new one by the same amount. Later in the code, the contents from the new sprite table are dumped into the old 32 byte one. At this point, you have both x and y free, so it is much faster and easier. See this: (made by psychopathicteen) http://wiki.superfamicom.org/snes/show/ ... ay+sprites

You know, I remember at one point, you said something about looking directly at the object table for object information in the metasprite routine. Because I was using both x and y in the routine, I figured that I wouldn't be able to directly pull information from the object table, so I had it to where I dumped metasprite related object stuff into different registers that I wouldn't have to index because they don't move. Can you please explain?
Re: SNES Programing Help 2
by on (#142833)
Espozo wrote:
That is what is "unofficially" called the "Soft OBC-1" technique. Because oam on the SNES is a pain in that there is a second sprite table for the 9th x bit and the sprite size bit, (see the relation?) psychopathicteen (unless he isn't actually the one) thought of the idea of creating a second sprite table that is just like the actual second sprite table in oam, except that this one is 512 bytes just like the first sprite table, so when you increment the pointer for the first sprite table, you are also incrementing the new one by the same amount. Later in the code, the contents from the new sprite table are dumped into the old 32 byte one. At this point, you have both x and y free, so it is much faster and easier. See this: (made by psychopathicteen) http://wiki.superfamicom.org/snes/show/ ... ay+sprites

I suspected there would be something like that. It just seemed like a bizzare way to go about it to me, if nothing else just for tying up those 512 bytes in prime direct-page WRAM real estate. Especially since I'm cramming my whole object list in that same $2000.

I just rotated each bit into a temporary register once I'd isolated them, and stored the register to the OAM2 table every time it fills up.
Espozo wrote:
You know, I remember at one point, you said something about looking directly at the object table for object information in the metasprite routine. Because I was using both x and y in the routine, I figured that I wouldn't be able to directly pull information from the object table, so I had it to where I dumped metasprite related object stuff into different registers that I wouldn't have to index because they don't move. Can you please explain?

What I meant is using direct page, so that you can just relocate your base address and then run the exact same code again to process another object. Like, say...
Code:
.EQU objectXVelocity    $00
.EQU objectYVelocity    $02

;at the start of each object as you loop through the list
lda AddressOfCurrentObject    ;make sure you're in 16-bit A mode
tcd                           ;set direct page (D) register to address of current object slot

;assuming you do your build-metasprite routine as you process each object...
;your direct page register will be set to the current object slot, so you just...

lda.b objectXVelocity    ;this will read from the address in the D register
lda.b objectYVelocity    ;this will read from the address in the D register plus $02
Re: SNES Programing Help 2
by on (#142836)
Khaz wrote:
I suspected there would be something like that. It just seemed like a bizzare way to go about it to me, if nothing else just for tying up those 512 bytes in prime direct-page WRAM real estate. Especially since I'm cramming my whole object list in that same $2000.I just rotated each bit into a temporary register once I'd isolated them, and stored the register to the OAM2 table every time it fills up.

That would be ever so slightly slower. It seems like with the SNES, if you can do just one less instruction even if you use 10 extra registers, than you do that, because the SNES has just about as much ram as you could ever possibly want, but not necessarily processing power.

Wait, oh yeah, so the direct page is kind of like an index register, even though it isn't at the end of the operand like x and y? why didn't they just do "lda Register,d"?
Re: SNES Programing Help 2
by on (#142840)
Espozo wrote:
Wait, oh yeah, so the direct page is kind of like an index register, even though you don't mess with it directly?

As far as I can recall off the top of my head, the only ways you interact with the D register are PHD/PLD and TCD/TDC, to set it or read it.

The effect of the D register is that every time you use a "Direct Page" instruction (generally speaking, an instruction with a one-byte operand like lda $10), the address in the D register is added to the operand (So if D is $1500 that lda reads from $1510).

It's simpler than it sounds (until you get into indirect addressing modes).
Re: SNES Programing Help 2
by on (#142858)
I just thought of a more efficient way of storing coordinates. "9.7" format. 9 bit X and Y coordinates, with 7 bits of subpixel accuracy. This would require a scrolling level map.
Re: SNES Programing Help 2
by on (#142859)
But then you create a lot of other problems in the code as you need to account for the wrapping.

It'd be probably better to store coordinates as 16-bit integer (no fractional part) and speeds as 8.8 fixed point, then when applying the momentum fake the subpixel accuracy through dithering over time.
Re: SNES Programing Help 2
by on (#142864)
I guess it depends how much continuity you want. If you're okay with objects resetting themselves, or doing one way scrolling, you could do it this way, but if you need to come back to objects later, then it doesn't work as well.
Re: SNES Programing Help 2
by on (#142878)
Okay, you know, I tried to start working on using direct page, but just after writing

Code:
lda ObjectOffset    ;make sure you're in 16-bit A mode
tcd                           ;set direct page (D) register to address of current object slot

It glitched up, and I didn't even write lda.b for anything yet, so I don't get it. I have two files, one called Metasprite and Metasprite Bad. (The sprite flipping isn't completed yet, so it looks really weird, but that isn't the problem.) Metasprite Bad is obviously the one that causes it to get jacked up. (It works fine until a bullet gets spawned by pressing B.) Game is using Metasprite, while Game Bad is using Metasprite Bad. I really don't have a clue what the problem is, because I've never done anything with direct page. I'd say look at bullet in Objects, and see Metasprite Bad.

Attachment:
MetaspriteDemoKoitsu.rar [305.75 KiB]
Downloaded 108 times
Re: SNES Programing Help 2
by on (#142880)
psycopathicteen wrote:
I guess it depends how much continuity you want. If you're okay with objects resetting themselves, or doing one way scrolling, you could do it this way, but if you need to come back to objects later, then it doesn't work as well.

I was actually thinking more about what would happen when a moving object crosses a boundary (which will inevitably happen).
Re: SNES Programing Help 2
by on (#142906)
Espozo wrote:
Okay, you know, I tried to start working on using direct page, but just after writing

Code:
lda ObjectOffset    ;make sure you're in 16-bit A mode
tcd                           ;set direct page (D) register to address of current object slot

It glitched up, and I didn't even write lda.b for anything yet, so I don't get it. I have two files, one called Metasprite and Metasprite Bad. (The sprite flipping isn't completed yet, so it looks really weird, but that isn't the problem.) Metasprite Bad is obviously the one that causes it to get jacked up. (It works fine until a bullet gets spawned by pressing B.) Game is using Metasprite, while Game Bad is using Metasprite Bad. I really don't have a clue what the problem is, because I've never done anything with direct page. I'd say look at bullet in Objects, and see Metasprite Bad.

Attachment:
MetaspriteDemoKoitsu.rar

Okay, I have to leave for the weekend here and I'm not sure I have either the time or motivation to go through your code and actually find your problem for you, but I do have some suggestions:

When you start using Direct Page for the first time, it will probably cause a bunch of problems if your assembler is anything like WLA. I don't know if that's the case, but with WLA, I had to add a ".b" on every single instruction referencing an address less than $0100, because WLA just assumed that a LDA $0050 meant LDA $50 from direct page. If you have ANY other instructions anywhere in your code that are being interpreted as a direct-page instruction incorrectly, they would have worked just fine before but will screw things up now that you start moving the direct page register. That might not be a problem for you in ca65.

So what to do? Very first idea is simply preserve the D register before and after your object routine. If you PHD at the very beginning of it, and PLD at the end, then whatever you do inside the routine the D register will be restored (presumably to 0000) by the end and stop screwing up the rest of your code. You can tighten this up to just one instruction for testing. Say,

phd
lda objectOffset
tcd
lda.b objectXPosition
pld

And then see if that one lda.b instruction was being carried out correctly. If you can get one to work you can get them all to work.

FFFFFFFFFFFF OH WAIT I JUST REMEMBERED doesn't ca65 have some kind of CRIPPLING bug with direct page where you have to compensate for how stupidly it interprets it? I completely forgot about that and I virtually guarantee there's your problem. I'm afraid you'll have to ask somebody who actually uses ca65 about that...
Re: SNES Programing Help 2
by on (#142914)
In ca65, you can use direct page addressing for any instruction that supports it with the < (low byte) operator. You just have to watch out for instructions that don't support direct page addressing, such as d,y (direct page indexed with Y) with anything other than STX or LDX.
Code:
  phd
  lda objectOffset
  tcd
  lda <objectXPosition
  pld
Re: SNES Programing Help 2
by on (#142924)
tepples wrote:
In ca65, you can use direct page addressing for any instruction that supports it with the < (low byte) operator. You just have to watch out for instructions that don't support direct page addressing, such as d,y (direct page indexed with Y) with anything other than STX or LDX.
Code:
  phd
  lda objectOffset
  tcd
  lda <objectXPosition
  pld

Yeah, that fixed it. (I haven't actually tried using it yet, I'm just saying I put it in and the game acts normally.) The thing is though, every time I want to load something using direct page, I have to use phd and pld? Isn't that not very efficient? I look at the object's x position, y position, and the several tile numbers for each sprite on a loop, all using direct page, so that's a problem.
Re: SNES Programing Help 2
by on (#142926)
You only have to push and pull D if you're returning to a routine that expects D not to have changed. And even then, you only need to push at the start and pull at the end.
Code:
  phd
  lda #0
loop:
  tcd
  ; OMITTED: bunch of stuff
  tdc
  clc
  adc #SIZEOF_OBJECT
  cmp #SIZEOF_OBJECT * MAX_OBJECTS
  bcc loop
  pld

Thus D is really just another indexing register. But if you're using D as the base pointer for an index into an array of structures in this manner, and you want to access variables in your actual zero page inside this loop, you'll need to make sure to use absolute mode (in ca65, a:some_label) or absolute long mode (f:some_label) for those variables, depending on where the data bank register B points.
Re: SNES Programing Help 2
by on (#142932)
Khaz wrote:
doesn't ca65 have some kind of CRIPPLING bug with direct page

I don't know if it qualifies as either crippling or a bug, but you do apparently have to pay attention because the memory segment feature is 6502-centric and only knows about zero page.
Re: SNES Programing Help 2
by on (#142955)
Ugg, Good Lord, what's wrong now? I tried to make object x and y positions use direct page, but it appears direct page is being loaded from the wrong location or something, because the values are 0 when they shouldn't be. There is now Game Bad or anything, I just have it to where Metasprite the sfc file is the messed up ones. Bullets don't spawn for some reason. (The code pretty much follows the players in terms of how it's set up, so I have no idea.)

Attachment:
MetaspriteDemoKoitsu.rar [291.67 KiB]
Downloaded 87 times

(Random, but I can't help but find the opcode phd a little funny.)
Re: SNES Programing Help 2
by on (#143042)
Wait, I looked at my code and it makes no sense. Just to get this straight, does direct page act exactly like x and y? I adjusted my code to where I wrote

Code:
  lda ObjectOffset
  tcd

and did

Code:
  adc <ObjectTable+YPosition

Later and it still doesn't work. (Man, why isn't there just a "z". :( ) I think I sort of know the reason only the player had worked and not the bullets though. The player is the first object.

Seriously, what is wrong with this?

Code:
.setcpu "65816"
.smart

; See Game.asm
.importzp Empty
.importzp MetaspriteTableOffset
.importzp MetaspriteDirection
.importzp MetaspritePalette
.importzp MetaspriteCharacterOffset
.importzp MetaspriteCount
.importzp HFlipMask
.importzp VFlipMask
.importzp SpriteCount
.importzp ObjectOffset
.import SpriteBuf1
.import SpriteBuf3
.import MetaspriteTable
.import ObjectTable

Identity = 0
XPosition = 2
YPosition = 4
XVelocity = 6
YVelocity = 8
Atributes = 10

; Used by Game.asm
.export start_metasprite

.segment "CODE"

;
; start_metasprite
;
; Upon entry:
;   X register = offset into MetaspriteTable to start at
;   Y register = offset to start writing at in SpriteBuf1
;
.proc start_metasprite
  phd
  rep #$30    ; A=16, X/Y=16
  ldy SpriteCount
  ldx MetaspriteTableOffset

  lda ObjectOffset
  tcd

  stz HFlipMask
  lda MetaspriteDirection
  bit #$0001
  beq metasprite_loop
  lda #$FFFF
  sta HFlipMask

metasprite_loop:
  cpy #$0200          ; sees if all 128 sprites are used up
  beq done
  and #$00FF
  sta SpriteBuf1+1,y        ; Store sprite Y position in SpriteBuf1+1,y
  lda MetaspriteCount       ; If MetaspriteCount is zero, then we're done.  Otherwise we have
  beq done                  ; metasprites to iterate over and populate for DMA (see VBlank)


  lda Empty,x               ; 1st byte = sprite X position (value 0-255)
  eor HFlipMask
  clc
  adc <ObjectTable+XPosition
  cmp #256
  bcc sprite_x_not_out_of_bounds
  cmp #65504
  bcs sprite_x_not_out_of_bounds
  bra sprite_out_of_bounds

sprite_x_not_out_of_bounds:
  and #$01FF
  sta SpriteBuf3,y          ; Store sprite X position SpriteBuf1+y
  and #$00FF
  sta SpriteBuf1,y          ; Store sprite X position SpriteBuf1+y
  lda Empty+2,x             ; 1st byte = sprite X position (value 0-255)
  eor VFlipMask
  clc
  adc <ObjectTable+YPosition
  cmp #224
  bcc sprite_y_not_out_of_bounds
  cmp #65504
  bcs sprite_y_not_out_of_bounds
  bra sprite_out_of_bounds

sprite_y_not_out_of_bounds:
  sta SpriteBuf1+1,y
  lda Empty+4,x
  sta SpriteBuf3+2,y        ; Sprite size
  lda Empty+6,x
  clc
  adc MetaspritePalette
  clc
  adc MetaspriteCharacterOffset
  sta SpriteBuf1+2,y        ; Palette/Character word
  txa
  clc
  adc #$0008
  tax
  iny
  iny
  iny
  iny
  dec MetaspriteCount       ; Decrement MetaspriteCount by 1
  bra metasprite_loop       ; Back to the loop...

sprite_out_of_bounds:
  txa
  clc
  adc #$0008
  tax
  dec MetaspriteCount       ; Decrement MetaspriteCount by 1
  bra metasprite_loop       ; Back to the loop...

done:
  sty SpriteCount       ; Says how many sprites have been made
  pld
  rts

.endproc
Re: SNES Programing Help 2
by on (#143079)
Khaz wrote:
FFFFFFFFFFFF OH WAIT I JUST REMEMBERED doesn't ca65 have some kind of CRIPPLING bug with direct page where you have to compensate for how stupidly it interprets it? I completely forgot about that and I virtually guarantee there's your problem. I'm afraid you'll have to ask somebody who actually uses ca65 about that...


ca65 uses 1 byte addressing (direct page) if either:
  • The address is a zeropage variable (in a .zeropage segment or marked zp with .globalzp or .importzp), or
  • .lobyte or < function is used, or
  • The variable is a constant and < $100, or
  • The variable is part of a struct and its address is < $100


What I do is define all of an object's variables in a struct

Header:
Code:
.struct NpcStruct
    state .addr

    xPos  .res 3
    yPos  .res 3

    ; ... etc ...
.endstruct

N_NPCS = 12

.global npcs


.segment "SHADOW"
    npcs:   .res .sizeof(NpcStruct) * N_NPCS



And access the fields through the struct, because they resolve to a constant < $100 they are in DP.
Code:
    ; DP = a NpcStruct object
    LDX NpcStruct::state

    LDA NpcStruct::xPos + 2
    BMI NegativeX

    ; ... etc ...



Also writing up a game loop
Code:
ProcessNpcs:

    PHD
    REP #$30
.A16
.I16
    LDA #npcs

@Loop:
        TCD

        LDX NpcStruct::state
        BEQ @Skip

        ; code to process NPCs

@Skip:
        TDC
        ADD #.sizeof(NpcStruct)
        CMP #npcs + N_NPCS * .sizeof(NpcStruct)
        BLT @Loop

    PLD
    RTS


(NOTE: you cannot nest structs if you use this pattern)

Espozo wrote:
Wait, I looked at my code and it makes no sense. Just to get this straight, does direct page act exactly like x and y? I adjusted my code to where I wrote

Code:
  lda ObjectOffset
  tcd

and did

Code:
  adc <ObjectTable+YPosition

Later and it still doesn't work. (Man, why isn't there just a "z". :( ) I think I sort of know the reason only the player had worked and not the bullets though. The player is the first object.

Seriously, what is wrong with this?


I think your problem is that your trying to access the Metasprite* variables (by .importzp) and the objects as a 1 byte address at the same time. When you set the DP register all 1 byte addresses will be added to DP to form the effective address.

Secondly, the value of ObjectTableis $0620 according to map.txt. Masking it to a 1 byte addresses will not work as you anticipate. What I think you are after is adc <YPosition after setting DP to #ObjectTable.

Lastly, by running the code through a trace logger it appears the value of ObjectOffset is 0, not $0620 (#ObjectTable) so there isn't any DP offsetting anyway.
Re: SNES Programing Help 2
by on (#143086)
UnDisbeliever wrote:
I think your problem is that your trying to access the Metasprite* variables (by .importzp) and the objects as a 1 byte address at the same time. When you set the DP register all 1 byte addresses will be added to DP to form the effective address.

What? :| What do you mean by "When you set the DP register all 1 byte addresses will be added to DP to form the effective address."? What are the "1 byte addresses?" How do I fix this particular problem?

UnDisbeliever wrote:
Secondly, the value of ObjectTableis $0620 according to map.txt. Masking it to a 1 byte addresses will not work as you anticipate. What I think you are after is adc <YPosition after setting DP to #ObjectTable.

Again, what?

UnDisbeliever wrote:
Lastly, by running the code through a trace logger it appears the value of ObjectOffset is 0, not $0620 (#ObjectTable) so there isn't any DP offsetting anyway.

If you're looking at the first code, I severely jacked it up. #ObjectTable is the offset of where the object table starts, and ObjectOffset is the offset that says what object we're on in the object table. (Sorry if the names weren't very clear...) Should I do

Code:
lda #ObjectTable
clc
adc ObjectOffset
tcd

and then just do something like

Code:
adc <YPosition

Latter?

Edit: I tried what I just suggested, and it still doesn't work. :?

Attachment:
MetaspriteDemoKoitsu.rar [291.7 KiB]
Downloaded 102 times

I guess it still doesn't work because of problems #1 and #2 you said?
Re: SNES Programing Help 2
by on (#143089)
Espozo wrote:
UnDisbeliever wrote:
I think your problem is that your trying to access the Metasprite* variables (by .importzp) and the objects as a 1 byte address at the same time. When you set the DP register all 1 byte addresses will be added to DP to form the effective address.

What? :| What do you mean by "When you set the DP register all 1 byte addresses will be added to DP to form the effective address."? What are the "1 byte addresses?"

It might be easier to understand with a concrete example. Assume D = $0880 and the CPU is executing the instruction adc $23. The "1-byte address" is $23. The CPU adds the value 35 ($23) to the current value of D ($0880), producing $08A3, and then uses $0008A3 as the address of the operand of adc.
Re: SNES Programing Help 2
by on (#143093)
So what's wrong then? Isn't what I've done correct? XPosition is 2, and I am adding 2 to the table offset + the object's offset.
Re: SNES Programing Help 2
by on (#143145)
Espozo wrote:
UnDisbeliever wrote:
I think your problem is that your trying to access the Metasprite* variables (by .importzp) and the objects as a 1 byte address at the same time. When you set the DP register all 1 byte addresses will be added to DP to form the effective address.

What? :| What do you mean by "When you set the DP register all 1 byte addresses will be added to DP to form the effective address."? What are the "1 byte addresses?" How do I fix this particular problem?

UnDisbeliever wrote:
Secondly, the value of ObjectTableis $0620 according to map.txt. Masking it to a 1 byte addresses will not work as you anticipate. What I think you are after is adc <YPosition after setting DP to #ObjectTable.

Again, what?

What he's getting at here is that it seems like you have other instructions that are also using direct page at the same time, so your D register is only set correctly for some instructions and the others are accessing the wrong address.

Regarding the second point specifically, it's referring to where you wrote earlier "adc <ObjectTable+YPosition". The problem with this (I THINK) is that the < is being applied immediately to ObjectTable and cutting out the hi byte, then adding YPosition. Either way, the better solution is definitely to put ObjectTable + ObjectOffset in D and then just "adc <YPosition", just like you said. A possibly even more betterer solution would be to store the absolute address of each object slot instead of just an offset from the start of the table. That way you don't have to add anything, just "lda ObjectSlotAddress", "tcd".

So, yeah, I'm guessing the first sentence there is probably your problem. Look through your code and make sure you don't have instructions using direct page addressing (ie/ a one-byte address for an operand) when they shouldn't be (I believe this is what listing files are good for). Make sure that the D register is set correctly for the instructions you DO want using direct page addressing (I like trace logs for that personally).
Re: SNES Programing Help 2
by on (#143147)
You can confirm some of Khaz's suspicions by looking at the generated assembly listing by ca65 (see relevant .lst file). I really am starting to sound like a broken record.

Footnote: I don't tend to recommend using the < shorthand operators in ca65, I tend to resort to using .LOBYTE(thing) or .LOWORD(thing) (there is also HIBYTE/HIWORD). And I say that after multiple decades of using < and > with older assemblers. Reference material: http://cc65.github.io/doc/ca65.html#s10
Re: SNES Programing Help 2
by on (#143152)
Espozo wrote:
What? :| What do you mean by "When you set the DP register all 1 byte addresses will be added to DP to form the effective address."? What are the "1 byte addresses?" How do I fix this particular problem?


Sorry about that, I was rushed for time.

Please read page 298 (Direct Page Addressing) of the 65c816 Programming Manual (PDF) for a diagram explaining what happens. Any load/store/cmp instruction that uses a single byte address is direct page.

You can check that by looking at the .lst files and count the number of bytes they are using.

Code:
000000r 1               .proc start_metasprite
000000r 1  0B             phd
000001r 1  C2 30          rep #$30    ; A=16, X/Y=16
000003r 1  A4 rr          ldy SpriteCount
000005r 1  A6 rr          ldx MetaspriteTableOffset
000007r 1               
000007r 1  A9 rr rr       lda #ObjectTable
00000Ar 1  18             clc
00000Br 1  65 rr          adc ObjectOffset
00000Dr 1  5B             tcd
00000Er 1               
00000Er 1  64 rr          stz HFlipMask
000010r 1  A5 rr          lda MetaspriteDirection
000012r 1  89 01 00       bit #$0001
000015r 1  F0 05          beq metasprite_loop
000017r 1  A9 FF FF       lda #$FFFF
00001Ar 1  85 rr          sta HFlipMask
00001Cr 1               



You can see that the SpriteCount, MetaspriteTableOffset, ObjectOffset, HFlipMask, MetaspriteDirection (and others) are using direct page addressing (1 byte addresses). Because you have set the DP register to something other than $0000, that value will be added to the address to form the effective one. This is probably causing some value in your object dataset to be overridden. When you read the same code through a trace logger you can see what I'm trying to explain.

Code:
$00/80C0 0B          PHD                     A:0000 X:8703 Y:0000 D:0000 DB:00 S:1FF9 P:envmxdiZC HC:0538 VC:231 FC:06 I:00
$00/80C1 C2 30       REP #$30                A:0000 X:8703 Y:0000 D:0000 DB:00 S:1FF7 P:envmxdiZC HC:0624 VC:231 FC:06 I:00
$00/80C3 A4 22       LDY $22    [$00:0022]   A:0000 X:8703 Y:0000 D:0000 DB:00 S:1FF7 P:envmxdiZC HC:0662 VC:231 FC:06 I:00
$00/80C5 A6 14       LDX $14    [$00:0014]   A:0000 X:8703 Y:0000 D:0000 DB:00 S:1FF7 P:envmxdiZC HC:0710 VC:231 FC:06 I:00
$00/80C7 A9 20 06    LDA #$0620              A:0000 X:8703 Y:0000 D:0000 DB:00 S:1FF7 P:eNvmxdizC HC:0758 VC:231 FC:06 I:00
$00/80CA 18          CLC                     A:0620 X:8703 Y:0000 D:0000 DB:00 S:1FF7 P:envmxdizC HC:0798 VC:231 FC:06 I:00
$00/80CB 65 26       ADC $26    [$00:0026]   A:0620 X:8703 Y:0000 D:0000 DB:00 S:1FF7 P:envmxdizc HC:0828 VC:231 FC:06 I:00
$00/80CD 5B          TCD                     A:0620 X:8703 Y:0000 D:0000 DB:00 S:1FF7 P:envmxdizc HC:0876 VC:231 FC:06 I:00
$00/80CE 64 1E       STZ $1E    [$00:063E]   A:0620 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdizc HC:0906 VC:231 FC:06 I:00
$00/80D0 A5 16       LDA $16    [$00:0636]   A:0620 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdizc HC:0960 VC:231 FC:06 I:00
$00/80D2 89 01 00    BIT #$0001              A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdiZc HC:1014 VC:231 FC:06 I:00
$00/80D5 F0 05       BEQ $05    [$80DC]      A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdiZc HC:1054 VC:231 FC:06 I:00
$00/80DC C0 00 02    CPY #$0200              A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdiZc HC:1092 VC:231 FC:06 I:00
$00/80DF F0 67       BEQ $67    [$8148]      A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:eNvmxdizc HC:1132 VC:231 FC:06 I:00
$00/80E1 29 FF 00    AND #$00FF              A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:eNvmxdizc HC:1164 VC:231 FC:06 I:00
$00/80E4 99 01 02    STA $0201,y[$00:0201]   A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdiZc HC:1204 VC:231 FC:06 I:00
$00/80E7 A5 1C       LDA $1C    [$00:063C]   A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdiZc HC:1266 VC:231 FC:06 I:00
$00/80E9 F0 5D       BEQ $5D    [$8148]      A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdiZc HC:1320 VC:231 FC:06 I:00
$00/8148 84 22       STY $22    [$00:0642]   A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdiZc HC:1358 VC:231 FC:06 I:00
$00/814A 2B          PLD                     A:0000 X:8703 Y:0000 D:0620 DB:00 S:1FF7 P:envmxdiZc HC:0048 VC:232 FC:06 I:00
$00/814B 60          RTS                     A:0000 X:8703 Y:0000 D:0000 DB:00 S:1FF9 P:envmxdiZc HC:0100 VC:232 FC:06 I:00



The effective address being read (the one in square braces) is the memory address being read for that instruction. In this case, your lda MetaspriteDirection (line $00/80D0) is actually reading address $00:0636 instead of the correct address of $00:0016 allocated by ca65. Thus its reading the wrong value and branching incorrectly.

Espozo wrote:
Edit: I tried what I just suggested, and it still doesn't work. :?

Attachment:
MetaspriteDemoKoitsu.rar

I guess it still doesn't work because of problems #1 and #2 you said?


Yeah. You solved problem 2 (wrong address), now you need to solve problem 1 (using DP addressing for non-object variables).

You can fix this by either moving the variables Empty, MetaspriteTableOffset, MetaspriteDirection, MetaspritePalette, MetaspriteCharacterOffset, MetaspriteCount, HFlipMask, VFlipMask, SpriteCount, NewObjectRequest, ObjectOffset out of .zeropage and into .BSS and replacing their .importzp/.exportzp with .import/.export.

Or you can force absolute addressing on these variables with the a: modifier.

Code:
  lda a:Empty,x               ; 1st byte = sprite X position (value 0-255)
  eor a:HFlipMask
  clc
  adc <XPosition
  cmp #256
  bcc sprite_x_not_out_of_bounds
  cmp #65504


Personally I prefer the first, but you may need to do the second sometime in the future (I have with the NPC vs player interaction code).

I also recommend you place the object variables in a struct (like I mentioned in my previous post), it makes the code a lot easier to manage if you have separate code for heroes and objects.

(sorry about not making this clearer).

Mod edit (koitsu): I updated the URL for the 65816 manual; WDC moved this somewhat recently.
Re: SNES Programing Help 2
by on (#143160)
And if you need to use indirection while you have direct page set elsewhere, you can push the address and use the (d,s),y addressing mode.
Re: SNES Programing Help 2
by on (#143168)
Yes! a: Worked! You have no idea how happy you all have made me! :mrgreen:
Re: SNES Programing Help 2
by on (#143185)
Espozo wrote:
Yes! a: Worked! You have no idea how happy you all have made me! :mrgreen:


No problem, we need more SNES developers in the world. :D
Re: SNES Programing Help 2
by on (#143198)
UnDisbeliever wrote:
Espozo wrote:
Yes! a: Worked! You have no idea how happy you all have made me! :mrgreen:


No problem, we need more SNES developers in the world. :D

Truer words have not been spoken.
Re: SNES Programing Help 2
by on (#143283)
Well, I've ran into another (thankfully much smaller ) dilemma. I have an "attributes" byte in every object slot that is exactly like bytes 3 and 4 in oam, and I was having it to where every time I press left, I have bit %0100000000000000 get loaded with a 1, and whenever I press right, I get bit %0100000000000000 loaded with a 1. I don't want this to interfere with any of the other bits. Left is easy in that I can do

Code:
  lda ObjectTable+Attributes,y
  ora #$4000
  sta ObjectTable+Attributes,y

But I can't for right, because I want the value to be 0, and ora only puts in 1s. Eor wouldn't work either, because it always flips the value, so it isn't really like the opposite of ora. I currently just do

Code:
  lda ObjectTable+Attributes,y
  ora #$4000
  eor #$4000
  sta ObjectTable+Attributes,y

But I was wondering if maybe there was a instruction that was like ora for 0s.
Re: SNES Programing Help 2
by on (#143285)
Espozo wrote:
But I was wondering if maybe there was a instruction that was like ora for 0s.

Just AND the opposite of the mask:
Code:
ora #%0100000000000000 ;sets bit 14
and #%1011111111111111 ;clears bit 14


EDIT: forgot the #s!
Re: SNES Programing Help 2
by on (#143287)
It's sad that that didn't occur to me...
Re: SNES Programing Help 2
by on (#143288)
Espozo wrote:
UnDisbeliever wrote:
Espozo wrote:
Yes! a: Worked! You have no idea how happy you all have made me! :mrgreen:


No problem, we need more SNES developers in the world. :D

Truer words have not been spoken.


Seconded. My own fandom of developing a SNES game is taking years to accomplish (My fandom is at least 10 years old and counting!), but I at least have one key component working in my favor: the music. I've been perfecting this, and I fully intend to bring out full length music once I make a working sound driver that can swap out data as needed (as well as implementing stopgaps in case swapping takes longer than expected).
Re: SNES Programing Help 2
by on (#143322)
KungFuFurby wrote:
Seconded. My own fandom of developing a SNES game is taking years to accomplish (My fandom is at least 10 years old and counting!), but I at least have one key component working in my favor: the music. I've been perfecting this, and I fully intend to bring out full length music once I make a working sound driver that can swap out data as needed (as well as implementing stopgaps in case swapping takes longer than expected).

I still have yet to make the SNES make a noise. I was thinking of just holding out until that SNES Tracker I keep hearing about is published/functional, but now I'm just trying to learn to do everything myself in raw assembly / bytes. I understand the BRR format no problem. I get the general idea of what I have to do - write code to operate the SPC700 and then write code to transfer that code over to it. It's just moving at a snail's pace due to the I would say EXTREMELY lacking documentation in this area. I learn roughly a thousand times faster when I have even just one functional example to look at and such a thing does not seem to exist for SNES music, at least not in the actual assembly language. You know, without decompiling a commercial ROM...

I honestly can't even find one single document that has all the information I need in it. I mean, it might be out there, but everything I've found is painfully incomplete and has to be read two or three times just to understand the less than half of the story it's telling you.
Re: SNES Programing Help 2
by on (#143342)
http://problemkaputt.de/fullsnes.htm

I use this one. Works well enough for my intentions.

Any details in particular you were wondering about?

I have three SPC700 assembly (well... two of them use non-native syntax) sound driver examples.

SNESGSS (caution, link may not last much longer, given that Google Code is in the process of shutting down to my knowledge... I think...) is a working sound driver. Syntax used is not native, though, and it compiles with bass (haven't tested it myself because I never compiled the program).

mukunda's SNESMod also counts for an example. This one uses TASM as an assembler, and uses native SPC700 syntax.

There's a third sound driver which again does not use native syntax. It also uses its own assembler. The sound driver is made by Paul Lay.
Home page for where the sound driver is used
The source is found in Cute Angel(a).
Re: SNES Programing Help 2
by on (#143398)
KungFuFurby wrote:
http://problemkaputt.de/fullsnes.htm

I use this one. Works well enough for my intentions.

Any details in particular you were wondering about?

I have three SPC700 assembly (well... two of them use non-native syntax) sound driver examples.

SNESGSS (caution, link may not last much longer, given that Google Code is in the process of shutting down to my knowledge... I think...) is a working sound driver. Syntax used is not native, though, and it compiles with bass (haven't tested it myself because I never compiled the program).

mukunda's SNESMod also counts for an example. This one uses TASM as an assembler, and uses native SPC700 syntax.

There's a third sound driver which again does not use native syntax. It also uses its own assembler. The sound driver is made by Paul Lay.
Home page for where the sound driver is used
The source is found in Cute Angel(a).


Thanks for this! I really appreciate it! I've sent you a PM re: a question on this. No need to reply, but just letting you know. Thanks.
Re: SNES Programing Help 2
by on (#143411)
Hey, Khaz!

Khaz wrote:
I learn roughly a thousand times faster when I have even just one functional example to look at and such a thing does not seem to exist for SNES music, at least not in the actual assembly language. You know, without decompiling a commercial ROM...

For a working homebrew SPC700 example, have a look at e.g. d4s's N-Warp Daisakusen sourcecode.

The results of an (attempted/incomplete?) disassembly of a commercial ROM's sound engine can be found at lytron's Bounty Sword SPC Disassembly.

Hope that helps! :D

Ramsis
Re: SNES Programing Help 2
by on (#143414)
Okay, first off thanks for all the links and such, it's been very helpful. I decided to try to use SNESGSS, since it was the only tracker option I found with actual WLA-compatible code. I've tried to set it up to play a sample song but it's not working yet. So far, it calls my "LoadMusic" routine which immediately calls "music_stop", which then calls "spc_command_asm". First thing "spc_command_asm" does after disabling interrupts is read APU0 repeatedly, waiting for it to become zero. APU0 is apparently always AA. I'm sure I'll find the answer if I go reading for a day again but it's probably easier to ask - is the source code wrong and I'm waiting for that AA, or is it the 00 and the SPC700 hasn't been initialized properly yet?

I haven't exactly done anything to write code to it to handle recieving data and interpreting commands, but how do you send it that code when it's not set up to begin with? The basic transfer protocol between the two processors is one thing I've never seen adequately described, at least not anywhere I can find anymore. I mean, fullsnes has this:
Code:
  Wait until Word[2140h]=BBAAh
  kick=CCh                  ;start-code for first command
  for block=1..num_blocks
    Word[2142h]=dest_addr   ;usually 200h or higher (above stack and I/O ports)
    Byte[2141h]=01h         ;command=transfer (can be any non-zero value)
    Byte[2140h]=kick        ;start command (CCh on first block)
    Wait until Byte[2140h]=kick
    for index=0 to length-1
      Byte[2141h]=[src_addr+index]      ;send data byte
      Byte[2140h]=index.lsb             ;send index LSB (mark data available)
      Wait until Byte[2140h]=index.lsb  ;wait for acknowledge (see CAUTION)
    next index
    kick=(index+2 AND FFh) OR 1 ;-kick for next command (must be bigger than
  next block  ;(if any)         ;         last index+1, and must be non-zero)
  [2142h]=entry_point           ;entrypoint, must be below FFC0h (ROM region)
  [2141h]=00h                   ;command=entry (must be zero value)
  [2140h]=kick                  ;start command
  Wait until Byte[2140h]=kick   ;wait for acknowledge

...which I assume is what I'm supposed to write for myself? It gives literally no explanation or context for this code. What am I even "uploading" here? SNESGSS outputs spc700 code for playing the music file, but since you overwrite that each time I'm assuming that can't be what's actually handling your commands and such on the spc700 side, so what is? There's also a "boot rom disassembly" on fullsnes... Which I assume is what's built into the spc700 and runs automatically on power on?

It would be really nice if the readme for SNESGSS had any information whatsoever on how its output is supposed to be used. For example, I happened to see in code that Ramsis posted a while back that you get the size of the music data from the first bytes in the .bin file. How would you be expected to know that? And how are you supposed to know when to use the SCMD_INITIALIZE command?

Also this is less important and a totally different topic, but... I did some reading today and happened to notice that BRL takes one cycle longer than JMP. I'm really confused in that case what the purpose of BRL is, since as I understand it both of them just redirect the program counter to any arbitrary address within the same bank - one by absolute and one by offset, but with the exact same result aside from one cycle.
Re: SNES Programing Help 2
by on (#143417)
The BRL and PER instructions are for position-independent code, which means code that can be copied into RAM at any address and still run.
Re: SNES Programing Help 2
by on (#143418)
Aha, yeah, it just occurred to me that if you're writing your branches with an actual number instead of a label it would make a difference for that. Even so, seems a bit odd to include an entire extra instruction in the set just for a matter of convenience like that. Maybe it mattered more in the days of less advanced tools / assemblers... Oh! I guess the idea is mostly for blocks of code stored in an already-assembled format, such that the label has to be definite and not just pasted as needed. Makes sense, in the context of the processor being used for more than just the SNES.

Now I have to change all my brls back to jmps again, when I get the motivation.

Anyways... Still reading...
Re: SNES Programing Help 2
by on (#143429)
KungFuFurby wrote:
Espozo wrote:
UnDisbeliever wrote:

No problem, we need more SNES developers in the world. :D

Truer words have not been spoken.


Seconded. My own fandom of developing a SNES game is taking years to accomplish (My fandom is at least 10 years old and counting!), but I at least have one key component working in my favor: the music. I've been perfecting this, and I fully intend to bring out full length music once I make a working sound driver that can swap out data as needed (as well as implementing stopgaps in case swapping takes longer than expected).


I've been doing this for 5 years, and I'm on my 4th attempt at making a game. Everytime, I learned from the mistakes I made in the past.
Re: SNES Programing Help 2
by on (#143432)
Okay I knew it was going to be something simple. You have to transfer the spc data WITHOUT issuing the load command the first time. It works now! Thank you again very much for all the help, I really appreciate it.

Can't get pause to work though, it just seems to stop it...
Re: SNES Programing Help 2
by on (#143580)
Khaz wrote:
Okay I knew it was going to be something simple. You have to transfer the spc data WITHOUT issuing the load command the first time. It works now! Thank you again very much for all the help, I really appreciate it.

Can't get pause to work though, it just seems to stop it...


Hi Khaz,

Can you post your player source code ?

I just started playing with snesgss yesterday, and am having similar problems trying to figure out how to get it's exported files into an emulator.

thx

Mike
Re: SNES Programing Help 2
by on (#143606)
Okay, so, most of what you need is inside the sneslib.asm file. When the system is powered up it's put into a default recieve-data state. The first thing you need to do is load the SNESGSS driver code ("spc700.bin") into it so that it can accept the commands that come with SNESGSS. I have a tiny function run once on power on to do that:
Code:
SPC_INITIAL_TRANSFER:
   php
   rep #$20         ;16 bit A

   lda #:spc700_code_01
   sta GSS_SPC_DATA_BANK

   lda #spc700_code_01+2
   sta GSS_SPC_DATA_OFFS

   lda.l spc700_code_01
   sta GSS_SPC_DATA_SIZE

   lda #$0200
   sta GSS_SPC_DATA_ADDR

   jsl spc_load_data      ;load snesgss spc code

   plp
   rtl

Which is pretty much just taken straight out of the larger main "load music" routine that I based on Ramsis's posted code from a while back. The only difference is that since the SPC boots into a recieve-data state, you don't (read: can't) issue the "LOAD" command the first time.
Code:
LoadMusic02:
   php

   jsl music_stop

   rep #$20         ;16 bit A

   lda.w #SCMD_LOAD
   sta gss_command
   stz gss_param
   jsl spc_command_asm      ;issue SCMD_LOAD command

   rep #$20         ;16 bit A

   lda #:spc700_code_02
   sta GSS_SPC_DATA_BANK

   lda #spc700_code_02+2
   sta GSS_SPC_DATA_OFFS

   lda.l spc700_code_02
   sta GSS_SPC_DATA_SIZE

   lda #$0200
   sta GSS_SPC_DATA_ADDR

   jsl spc_load_data

   rep #$20         ;16 bit A

   lda.w #SCMD_LOAD
   sta gss_command
   stz gss_param
   jsl spc_command_asm      ;issue SCMD_LOAD command

   rep #$20         ;16 bit A

   lda #:music_02_data
   sta GSS_SPC_DATA_BANK

   lda #music_02_data+2
   sta GSS_SPC_DATA_OFFS

   lda.l music_02_data
   sta GSS_SPC_DATA_SIZE

   lda.l spc700_code_02+14
   sta GSS_SPC_DATA_ADDR

   jsl spc_load_data

   rep #$20         ;16 bit A

   lda.w #SCMD_INITIALIZE
   sta gss_command
   stz gss_param
   jsl spc_command_asm

   rep #$20         ;16 bit A

   lda.w #SCMD_STEREO
   sta gss_command
   lda.w #$0001
   sta gss_param
   jsl spc_command_asm

   rep #$20         ;16 bit A

   lda #$00FF
   sta GSS_SPC_VOL_FADESPD

   lda #$007F
   sta GSS_SPC_VOL_CURRENT
   jsl spc_global_volume

   rep #$20         ;16 bit A

   lda.w #SCMD_MUSIC_PLAY
   sta gss_command
   stz gss_param
   jsl spc_command_asm

   sep #$20         ;8 bit A

   plp
   rtl

I had to make some adjustments to load_data to fit my build as I don't have free space on the "zero page", and stack-relative addressing kinda freaks me out, so I just allocated some variables instead of their method of passing data into the load_data subroutine (so say "lda 7,s" becomes "lda GSS_SPC_DATA_SIZE"). I also had to move the direct page just to use the one "direct page indirect Y" (sic, the one with the []s) instruction.

(On that subject, how is it intended that you use the stack-relative addressing SNESGSS is natively written in? I saw in the 65816 "manual" an example like:
Code:
    jsl subRoutine
    .DC parameter
returnAddress:

I'm not familiar with "DC", and it seems a pain to retrieve it this way on the other end. Only other thought is to push before the subroutine call and pull after.)

Note also as was said in the earlier thread, anytime you're reading an APU register waiting to see a specific value, you have a very narrow time window to catch it so you can't afford to have an interrupt hit and make you miss it. So, in spc_command_asm and anywhere else you're doing that, you'll have to add in the same disable-interrupts bit like in load_data.

Anyways. That's about as far as I've got. I haven't attempted to use the SFX_play function yet. I need to figure out how to build a sound effect first. If you can't get it to work let me know where you've hit a problem and I'll try to help.
Re: SNES Programing Help 2
by on (#143649)
Khaz wrote:
On that subject, how is it intended that you use the stack-relative addressing SNESGSS is natively written in?


I spent a little while looking at SNESGSS last week and noticed it is meant to be called using the tcc-65816 C compiler syntax (Google Code Project).

  1. Push values to stack
  2. JSL routine (with 16 bit A and Index)
  3. Move stack pointer back

Running a few examples through the compiler we get:

Code:
; void spc_command(unsigned int command,unsigned int param);
; spc_command(12, 34);

pea.w 34
pea.w 12
jsr.l spc_command
tsa
clc
adc #4
tas


Code:
; void spc_command(unsigned int command,unsigned int param);
; int p;
; spc_command(p, 34);

pea.w 34
lda 0 + __main_locals + 1,s
pha
jsr.l spc_command
tsa
clc
adc #4
tas


Code:
; void copy_to_vram(unsigned int adr,const unsigned char *src,unsigned int size);
; extern static unsigned char *source;
; copy_to_vram(0x4000, source, 256);

pea.w 256
lda.l __tccs_source + 0
sta.b tcc__r0
lda.l __tccs_source + 0 + 2
pha
pei (tcc__r0)
pea.w 16384
jsr.l copy_to_vram
tsa
clc
adc #8
tas


Two things to note:
  • The normal mode of the compiler is 16 bit A, 16 bit Index
  • All pointers are 4 bytes, the MSB is blank
Re: SNES Programing Help 2
by on (#143652)
Hey, Khaz,

what UnDisbeliever says. :)

Personally though, I don't favor using the stack for passing multiple parameters to a subroutine either, the two major disadvantages being 1) the increased risk of crashes related to stack overflow, and 2) the necessity to correct the stack pointer each and every time. So currently, I use variables just like you do.

This, however ...

Khaz wrote:
Code:
    jsl subRoutine
    .DC parameter
returnAddress:


... is a great way to pass e.g. a text string to a subroutine. In fact, I incorporated this method into my WIP PowerPak codebase a while back, shortening the PrintString macro to basically what you posted. :)

The PrintF subroutine now processes text strings like so:

Code:
PrintF:
   plx               ; pull return address (-1) from stack, which is actually the string address (-1)
   inx               ; make X = start of string

   ----- snip -----
   [string processing with X as char pointer, X register eventually points to the NUL terminator]
   ----- snip -----

   phx               ; push return address (-1) onto stack
rts


Neat, huh? ;) I got the idea from the official WDC 65816 program manual (pp. 182-191), generally a recommended read! :)

Ramsis
Re: SNES Programing Help 2
by on (#143653)
UnDisbeliever wrote:

I spent a little while looking at SNESGSS last week and noticed it is meant to be called using the tcc-65816 C compiler syntax (Google Code Project).



Where did you "notice" that ?

The readme.txt included with the SNESGSS editor just has some brief information on the export formats of the files, and load addresses etc.

Looks like something messy / unfinished :(.

I.e. compared to the "hi level" nature of the music editor, the export functions are "unfinished" / low-level.

Have you managed to import those files into an emulator / real SNES ?

mic_ has posted a working SPC player here :

http://jiggawatt.org/badc0de/spcplayer-1.3.zip (thread : viewtopic.php?f=12&t=5765&start=60)

But that is for a "standard 64k SPC file" or at least "common " ?

Maybe some of that code can be used to play SNESGSS's spc700.bin file ?

Mike
Re: SNES Programing Help 2
by on (#143670)
mstram wrote:
UnDisbeliever wrote:

I spent a little while looking at SNESGSS last week and noticed it is meant to be called using the tcc-65816 C compiler syntax (Google Code Project).



Where did you "notice" that ?


Experience.

sneslib.h is a C header file for sneslib.asm. sneslib.asm also contains tcc temp registers (tcc__r0) and __tcc_ prefixes for the global variables.


mstram wrote:
Have you managed to import those files into an emulator / real SNES ?


No I have not, I currently focusing my efforts to learn 65816 game programming patterns and hoping (fingers crossed) that someone else (looking at Khaz :) ) manages to improve the state of homebrew SNES Sound code.
Re: SNES Programing Help 2
by on (#143696)
Hey, Mike!

mstram wrote:
Have you managed to import those files into an emulator / real SNES ?

There are indeed people who have. :wink:

mstram wrote:
Maybe some of that code can be used to play SNESGSS's spc700.bin file ?

SNESGSS contains a full-fledged SNES sound driver of its own, i.e. to even use SNESGSS for making SNES music implies that you won't be needing to bother about any other SPC700 driver for uploading the software's output to the SPC700 (in fact, that's the whole point of SNESGSS). :)

So after everything that's been posted so far about getting SNESGSS to work, I'm afraid you'll have to get a tad more specific on what doesn't work for you, exactly. :wink:

Ramsis
Re: SNES Programing Help 2
by on (#146240)
I've had a couple issues lately testing my program that uses SNESGSS. Specifically, sometimes something will go very wrong the first time it tries to play "music", usually just some very loud brief static right before the audio, sometimes it's silent, last time I tried it the audio itself didn't play right and went on for a while glitching up pretty badly. Another attempt and the game just froze. After the first time triggering a sound though, the audio always plays fine from then on until power off and on again.

I don't know whether this is something wrong with my code, perhaps something not initialized right with the SPC, or if this is a problem with my SD2SNES. I can't reproduce it on emulator, and I DID have problems with the SD2SNES repeatedly crashing in games when I tried to use it on someone else's SNES a while back... I thought at the time maybe a bad cartridge slot connection but now I'm not so sure.

Can anyone think of something obvious I might be missing? Or should I be contacting the manufacturer thinking it's a hardware problem?

EDIT: Just noticed there's been a slight firmware update. Will try that first.

... Nope. 0.1.7 preview 3 doesn't fix it.
Re: SNES Programing Help 2
by on (#146269)
Is the "loud brief static" the previous contents of the echo buffer? And is the lockup caused by echo buffer logic overwriting your program?
Re: SNES Programing Help 2
by on (#146276)
I have no idea, since I can only cause it to happen on the physical SNES, not on emulator...
Re: SNES Programing Help 2
by on (#146304)
Does this engine use the echo effect at all? If so, does the instability on hardware go away if you disable echo?
Re: SNES Programing Help 2
by on (#146363)
Aha. Well if it's echo that's causing the problem then I found out why. Here's an excerpt from "DSPInitData:" in SNESGSS's "spc700.asm" file (which I understand to be a transcript of the actual spc700 driver code) as of the version I've been using:
Code:
   db {DSP_EON}  ,255                  //echo enabled
   db {DSP_EVOLL},0                  //echo (as global) volume to zero, it gets to the max after init
   db {DSP_EVOLR},0
   db {DSP_KOF}  ,255                  //all keys off
   db {DSP_DIR}  ,sampleDirAddr/256      //sample dir location
   db {DSP_FLG}  ,%00000000            //no mute, enable echo
   db 0

That version was uploaded in December. As of the end of March there's a new version which changed to:
Code:
   db {DSP_EON}  ,0                  //echo disabled
   db {DSP_EVOLL},0                  //echo volume to zero
   db {DSP_EVOLR},0
   db {DSP_KOF}  ,255                  //all keys off
   db {DSP_DIR}  ,sampleDirAddr/256      //sample dir location
   db {DSP_FLG}  ,%00100000            //no mute, disable echo
   db 0

Looks like it was previously using the Echo volume for something, not too sure why.

I will try the latest SNESGSS when I get a chance/find it and see if it fixes everything.

EDIT: Had code samples backwards. >.<