6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Nov 23, 2024 12:45 pm

All times are UTC




Post new topic Reply to topic  [ 13 posts ] 
Author Message
PostPosted: Tue Mar 26, 2024 6:12 am 
Offline

Joined: Tue Mar 26, 2024 5:41 am
Posts: 4
Hello,

I am going through some code that is a tutorial for 6502 Assembly NES game programming called "Nerdy Nights". In it there is a piece of code that I am having trouble understanding, which uses a 2 byte lookup table for indexing.

Here is part of the lookup table and the code;
Code:
note_table:
    .word $07F1, $0780, $0713
    .word $06AD, $064D, $05F3, $059D, $054D, $0500, $04B8, $0475, $0435, $03F8, $03BF, $0389

    lda #$0C            ;the 13th entry in the table (A2)
    asl a                   ;multiply by 2 because we are indexing into a table of words
    tay
    lda note_table, y   ;read the low byte of the period
    sta $4002            ;write to SQ1_LO
    lda note_table+1, y  ;read the high byte of the period
    sta $4003               ;write to SQ1_HI


The NES has two 8 bit registers (in it's audio processing unit, APU) which are $4002 & $4003 that are used to store an 11 bit value that makes up the musical note of the NES's sound wave generator. So, we need to access two bytes from the lookup table to store into these registers ($4002 & $4003). to play a musical note.

In the code above, why can't we just load the y register with the value #$18 (24 decimal), rather than;
lda #$0C ;load a with 12
asl a ;shift left to multiple by 2, now A contains 24 dec (#$18)
tay
lda note_table, y ;read the low byte of the period, y = 24 and loads the value $03 from the note table above
sta $4002 ;write to SQ1_LO, write $03 to $4002
lda note_table+1, y ;read the high byte of the period
sta $4003 ;write to SQ1_HI, write second byte $f8 (from note table) to $4003

Thanks for any help anyone might be able to offer.
TonyAme


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 26, 2024 8:24 am 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1488
Location: Scotland
I know nothing about the NES, however...

Could it be that this code is/was part of a subroutine, so the calling code can just pass in the note to play and have the subroutine do the rest?

But then the calling code could pass in the doubled value too...

Maybe the calling code just increments a single byte sequence counter to play a tune?

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 26, 2024 8:54 am 
Offline

Joined: Mon Jan 19, 2004 12:49 pm
Posts: 985
Location: Potsdam, DE
In short, I see no reason why not... it's an immediate load so it can be anything, and it can be direct to Y as easily to A or X.

A is eaten by the following code, so there's no need to set it at all... though you point out this is a tutorial. In which case, it's pointing out a generic approach to loading a word from a table when the offset is provided in A as an entry count.

So the initial load of A would not be used in 'real' code but an offset might well be passed in A to a subroutine, which calculates the offset. This code just demonstrates how that calculation is performed.

(One caveat with this approach: you are limited to a table with a maximum of 128 entries...)

Neil


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 26, 2024 10:16 am 
Offline
User avatar

Joined: Tue Feb 28, 2023 11:39 pm
Posts: 257
Location: Texas
drogon wrote:
I know nothing about the NES, however...

Could it be that this code is/was part of a subroutine, so the calling code can just pass in the note to play and have the subroutine do the rest?

But then the calling code could pass in the doubled value too...

Maybe the calling code just increments a single byte sequence counter to play a tune?

-Gordon


Pretty sure this is the case where they took a function that was designed to be called with a parameter as Gordon said.

At the extreme end of this you could just write the values out yourself without a look up table at all if you just needed to play that one note:

Code:
lda #$04
sta $4002
lda #$35
sta $4003


However that generally wouldn't be terribly useful if you're reading a set of notes from a music file, in which case you'd need to call the function to change the note as the music is playing.

Consider:

Code:
music_data:
    .byte $01 $02 $03 (and so on)

note_table:
    .word $07F1, $0780, $0713
    .word $06AD, $064D, $05F3, $059D, $054D, $0500, $04B8, $0475, $0435, $03F8, $03BF, $0389

play_music:
    lda #0
    tax
rm_loop:
    lda music_data, x
    jsr play_note
    inx
    bcc rm_loop ; Let's pretend we have 256 notes for this example.


play_note:
    asl a
    tay
    lda note_table, y
    sta $4002
    lda note_table+1, y
    sta $4003
    ret


Note I haven't actually tried this code, I just wrote it off the top of my head; I don't have anything setup for doing NES development. Hopefully it at least illustrates the idea.

Now, there is no reason you can't store your music with the values already doubled if you want to. And perhaps if you need to squeeze out some clock cycles for a game loop that wouldn't be a bad idea. After all the music in your game is not going to get read by anything but your game.

Edit:
I was thinking about this a bit further.

The NES/Famicom featured an expansion port on the bottom of the consoles back when they were released the 80s. (I don't think any of the newer slimmed down versions had it though). Given the reverse engineering I've seen on the NES, I would wager it was used primary for developers who needed to debug/test things, and I would not be at all shocked if somewhere, someplace, buried deep in the Nintendo dev kits of the day, there was a MIDI connector so you could use a MIDI keyboard for composing game music and hear it actually playing on the NES itself.

The MIDI standard defines 128 notes, fitting nicely in 7 bits. Which might explain where the original code may of come from. (Just food for thought)


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 26, 2024 2:50 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8509
Location: Midwestern USA
Yuri wrote:
The MIDI standard defines 128 notes, fitting nicely in 7 bits. Which might explain where the original code may of come from. (Just food for thought)

...which amounts to 10-1/2 octaves, in case you were wondering.  For perspective, a large pipe organ has an approximate 9 octave range if it has a 32-foot pedal stop.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 26, 2024 2:58 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8509
Location: Midwestern USA
tonyame wrote:
In the code above, why can't we just load the y register with the value #$18 (24 decimal)...

As the others have noted, the routine to drive the sound hardware has a degree of generality to it.  If you load .Y with a static value and skip the ASL — TAY business, you’ve got a one-note wonder.  :D  I’d think that would get boring really fast.

Incidentally, good programming practice frowns on placing hard-coded numbers into code.  Aside from the fact that doing so makes the program’s purpose and function somewhat opaque to someone else reading your source code, it complicates modifications and opens the door to insidious bugs caused by mistyping numbers that are buried somewhere in the middle of the program.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 26, 2024 5:05 pm 
Offline

Joined: Sun Nov 08, 2009 1:56 am
Posts: 411
Location: Minnesota
Also note that if the 'ASL' step (and its two-cycle execution time) bothers you, you can split the note table in two:

Code:
note_table_lo:
    .byte $F1, $80, $13
    .byte $AD, $4D, $F3, $9D, $4D, $00, $B8, $75, $35, $F8, $BF, $89

note_table_hi:
    .byte $07, $07, $07
    .byte $06, $06, $05, $05, $05, $05, $04, $04, $04, $03, $03, $03


and then:

Code:
play_note:
    tay
    lda note_table_lo, y
    sta $4002
    lda note_table_hi, y
    sta $4003
    rts


MIDI may still give you only 128 notes, but you could now play a tune of up to 256 of them without much trouble.


Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 26, 2024 6:12 pm 
Offline
User avatar

Joined: Sun Nov 01, 2020 10:36 am
Posts: 37
Location: Tatooine
I found the source page: https://nerdy-nights.nes.science/#audio_tutorial-3

Those values are not exactly notes but periods, so - almost - the inverse of the frequency.

The table contains 16bit values because it's simpler to write it like that. Or you can split it in two tables, as teamtempest showed.
So - as barnacle said - it's just an example for how to read a double byte from a table of words (2 bytes values).
That snippet is generally fed by a number that indicates the Nth value in the table; but since the table is made of words there's the need to double the input value, and that is done by the "ASL" instruction: so the displacement of the following LDA note_table,y refer to the 2 x Nth position.

______
Edit:
really? I wrote "barnacled" instead of "barnacle"... :( Apologies... :|


Last edited by BB8 on Wed Mar 27, 2024 7:43 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Tue Mar 26, 2024 10:37 pm 
Offline

Joined: Tue Mar 26, 2024 5:41 am
Posts: 4
Thank you for the great help on this. I am just learning the 6502 asm, so that bit of code confused me. I thought there was some cryptic reason why we had to use the ASL before retrieving values from the note table. I think I'm seeing that this was just for demonstration (tutorial) purposes.

Much clearer now, sincere thanks.

TonyAm


Top
 Profile  
Reply with quote  
PostPosted: Wed Mar 27, 2024 9:26 pm 
Offline

Joined: Sun Jul 28, 2013 12:59 am
Posts: 235
Yuri wrote:
The NES/Famicom featured an expansion port on the bottom of the consoles back when they were released the 80s. (I don't think any of the newer slimmed down versions had it though). Given the reverse engineering I've seen on the NES, I would wager it was used primary for developers who needed to debug/test things, and I would not be at all shocked if somewhere, someplace, buried deep in the Nintendo dev kits of the day, there was a MIDI connector so you could use a MIDI keyboard for composing game music and hear it actually playing on the NES itself.

The MIDI standard defines 128 notes, fitting nicely in 7 bits. Which might explain where the original code may of come from. (Just food for thought)

The expansion port was never "officially" used, but was apparently intended to make bringing the disk system to the US straightforwards without having to have a cable coming out the front of the machine to the disk drive. The main aftermarket use for it is adding a resistor (often a potentiometer) to allow famicom games with external audio to be heard on a NES (the famicom routes the audio path through the cartridge connector, the NES doesn't, but some number of converters and flash cartridges route the famicom cartridge audio output to one of the connections for the expansion port, and the expansion port also has access to the built-in audio, again so that the disk system audio could be made to function), or to map a famicom-style expansion port so that an external keyboard, cassette recorder, or set of 3D glasses can be used.

That said, if you want an example of MIDI on the NES, the Miracle Piano Teaching System used a scheme where a shift register attached to the first player controller port was used to clock MIDI data in and out under control of the NES. The same scheme was presumably used for the Genesis and Super Nintendo versions of the system, and there were RS-232 (and RS-422?) based versions for PC/Mac systems that didn't necessarily have "proper" MIDI ports.


Top
 Profile  
Reply with quote  
PostPosted: Wed Mar 27, 2024 10:45 pm 
Offline

Joined: Tue Mar 26, 2024 5:41 am
Posts: 4
nyef wrote:
Yuri wrote:
That said, if you want an example of MIDI on the NES, the Miracle Piano Teaching System used a scheme where a shift register attached to the first player controller port was used to clock MIDI data in and out under control of the NES. The same scheme was presumably used for the Genesis and Super Nintendo versions of the system, and there were RS-232 (and RS-422?) based versions for PC/Mac systems that didn't necessarily have "proper" MIDI ports.


Was the Miracle Piano a MIDI controller? I mean did it use MIDI TX data, and then convert/send that data into a 4021 shift register? Or was it the other way around, shift register (inside piano) --> convert to MIDI TX --> to expansion port?

Very interesting.

Thanks,
TonyAm


Top
 Profile  
Reply with quote  
PostPosted: Thu Mar 28, 2024 5:39 pm 
Offline

Joined: Sun Jul 28, 2013 12:59 am
Posts: 235
tonyame wrote:
Was the Miracle Piano a MIDI controller? I mean did it use MIDI TX data, and then convert/send that data into a 4021 shift register? Or was it the other way around, shift register (inside piano) --> convert to MIDI TX --> to expansion port?

The keyboard itself had MIDI in and out ports, plus a 25-pin D-Sub connector that had serial (9600 baud?) plus a synchronous shift register of some sort. Depending on your setup, you'd use one of those three connectivity options. For video game systems specifically, they used the synchronous shift register connected to a controller port (not an expansion port) on the game system, but it's entirely MIDI data for the protocol.


Top
 Profile  
Reply with quote  
PostPosted: Thu Mar 28, 2024 11:16 pm 
Offline

Joined: Tue Mar 26, 2024 5:41 am
Posts: 4
nyef wrote:
tonyame wrote:
Was the Miracle Piano a MIDI controller? I mean did it use MIDI TX data, and then convert/send that data into a 4021 shift register? Or was it the other way around, shift register (inside piano) --> convert to MIDI TX --> to expansion port?

The keyboard itself had MIDI in and out ports, plus a 25-pin D-Sub connector that had serial (9600 baud?) plus a synchronous shift register of some sort. Depending on your setup, you'd use one of those three connectivity options. For video game systems specifically, they used the synchronous shift register connected to a controller port (not an expansion port) on the game system, but it's entirely MIDI data for the protocol.


Thanks for the info. If I'm understanding this correctly, the MIDI data TX ---> to synchronous shift register ---> to controller port is very interesting.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 13 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 18 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: