Offline
Sea of Souls

Can anyone point me in the right direction? I am attempting to map a value range to another value range as explained here, but I wish to do so in Assembly; more specifically, in GBZ80 ASM.

EDIT: Also worth mentioning, I don't want to use a look-up table because it would be massive for my purposes. I just need to scale a 'linear' 8-bit number from 0x00 - 0x255 to an 'exponential' 11-bit number from 0x00 - 0x7FF.

Can I Load the number into a register, (eg. A) and shift it to the left once? Obviously I need to worry about the three bits of carry too.

Last edited by Orgia Mode (Oct 16, 2018 12:59 am)

Offline
Nomad's Land

What exactly do you want to do? Sounds like you're trying to generate note frequency dividers. If that's the case I'd definately recommend using a lookup table. It's gonna cost you 512 bytes at most.

Otherwise, just ld a,n, add a,a, rotate the carry into another register, rinse and repeat.

Offline
Sea of Souls
irrlichtproject wrote:

What exactly do you want to do? Sounds like you're trying to generate note frequency dividers. If that's the case I'd definately recommend using a lookup table. It's gonna cost you 512 bytes at most.

Otherwise, just ld a,n, add a,a, rotate the carry into another register, rinse and repeat.

Thanks for the reply!
I am trying to receive serial data and convert it to a frequency, yes.
Now that I think about the size of a lookup table in actual quantized bytes, it may not be as large as I had originally thought. Since I have to worry about two registers, one 8-bits and one 3-bits (11-bits in total) that can equate to 256+8 numbers, so 512 was a very generous guess.

Now the next question: which method would require the fewest cpu cycles?
Once I get to this point in the code, I will write up both routines and add up the cpu cycles for each.
Again, thanks for the input!

-Cheers,
-Fox

Offline
Nomad's Land

Table lookup will win hands down in terms of cycle count, since calculating note frequencies involves several very costly mathematical operations.
Expanding the table to 512 bytes will help squeeze a few more cycles, if needed. Generally the idea is to split low and high bytes of the lookup values, and align each table to a 256 byte border, so you can directly translate the indices, like so

   ld h,table_base_lo_bytes>>8
   ld l,a     ; set index
   ld c,(hl)   ; load lo-byte
   ld h,table_base_hi_bytes>>8   ; or just inc h if hi-byte lut follows on next page
   ld b,(hl)    ; load hi-byte, frequency divider now in BC

Last edited by irrlichtproject (Oct 16, 2018 9:33 pm)

Offline
Sweeeeeeden

What's the goal here? Are you trying to build something that is musically useful? If so, you may wish to make it tunable to the standard ET12 (equal temperament, 12 tone) scale. Gameboy has about 6 octaves of useful range. Lower than C3, and the hardware can't reproduce it. Higher than octave 8, an the pitch precision becomes so low that you can no longer produce pure tones. That gives you about 70 notes that you'd want to hit, and then you can fill in the rest of the values between those, and/or extend the scale upward.

For example Imagine that every third value (0,3,6,9...) is a semitone, and the other values are pitches in-between. This gives about 256/3=85 available semitones, ranging from C3 to C#10 (~8.7 kHz) and 2 additional untuned values between each semitone.

Example Python code:

import math

# MIDI index to frequency
def m2f(m):
    return 440*math.pow(2,(m-69)/12.)

# Frequency in Hz to GB pitch value
def f2g(f):
    return 2048-(131072./f)

# GB pitch value to frequency in Hz
def g2f(g):
    return 131072./(2048-g)

print ("Oct\tNote\tFreq (Hz)\tGB value")

# Print example data for human consumption
for i in range(256):
    print "{0}\t{1}\t{2}\t{3}".format(i/3/12, ((i/3)%12)+1, m2f(36+i/3.), int(round(f2g(m2f(36+i/3.)))))

# Print assembly data
for i in range(256):
    print "\tdw\t{0}".format(int(round(f2g(m2f(36+i/3.)))))
Offline
Sea of Souls

Thats very helpful, nitro. I will shoot for all 6-octaves, however I do want my program to be able to produce every possible frequency within the constrains of the cpu (64-131072Hz), given user's input. And yes, it is for musical purposes.
When you say "pure tones" do you mean only the classically tuned notes within the ET12? After C#10 can it no longer precisely maintain the 12 tones?

I am just about to the point in code to begin using the lookup table.

Offline
Sea of Souls

I just threw this together in excel and I think it should work for me, if not at least for initial tests. It is 2048 numbers, increasing from 0 - 2047 exponentially (rounded up to the next whole number). I might need to shift it to align with a scale, but I'm currently happy although its bigger than I originally planned.

Text wall:

› Show Spoiler

EDIT: Crap...I did taht wrong. I have to translate an 8-bit number to an 11 bit number, not 11 to 11. sad

Last edited by Orgia Mode (Oct 20, 2018 1:18 am)

Offline
Sea of Souls

Alright, I finally figured out the math I needed. Now all I have to do is shift the bass note around until I get the notes I want to hear I guess.

› Show Spoiler

I have primed all of the registers to be ready for user input and found that the noise channel is the most fun to play with! tongue
Most noteable the AUD4POLY register. The broad range sund taht it can produce is outstanding.

@nitro, is that what shit wave does? Just inject horseshit into the polynomial counter?

EDIT: Shower thought, why is NR20 unused while NR10 (sweep) is present? The corresponding bytes in I/O are simply ignored, along with several other bytes, but this one in particular seems to have been planned ahead. A missed opportunity.

Last edited by Orgia Mode (Oct 23, 2018 9:24 pm)

Offline
Sweeeeeeden
Orgia Mode wrote:

@nitro, is that what shit wave does? Just inject horseshit into the polynomial counter?

Nope. Shitwave is actually using the wave channel and the shift register it's using is all software.

Orgia Mode wrote:

EDIT: Shower thought, why is NR20 unused while NR10 (sweep) is present? The corresponding bytes in I/O are simply ignored, along with several other bytes, but this one in particular seems to have been planned ahead. A missed opportunity.

That would be to save silicon space on the chip. Though I'm not sure how much difference it would have made.

Offline
Sea of Souls

Finally figured it out...ish.

As I stated before, I was taking a number between 0 and 255 and assigning a frequency value, but the gameboy needs an 11-bit number for the registers  AUD1LOW and AUD1HIGH (and others). So I then took my frequency table and converted it BACK into a digital value with x=2048-(131072/Hz).

expoTable EQU $3000

SECTION "expotable",HOME[$3000]
;expoTable ;  512 bytes (256 integers)
; This table is not perfect by any means. I'll make the final "tuning" adjustments later.
DW 44, 98, 150, 201, 250, 299, 346, 391, 436, 479, 521, 562, 602, 641, 678, 715, 751, 786, 819, 852, 884, 916, 946, 976, 1004, 1032, 1060, 1086, 1112, 1137, 1161, 1185, 1208, 1231, 1253, 1274, 1295, 1315, 1335, 1354, 1372, 1391, 1408, 1425, 1442, 1458, 1474, 1489, 1504, 1519, 1533, 1547, 1560, 1574, 1586, 1599, 1611, 1622, 1634, 1645, 1656, 1666, 1676, 1686, 1696, 1706, 1715, 1724, 1732, 1741, 1749, 1757, 1765, 1773, 1780, 1787, 1794, 1801, 1808, 1814, 1820, 1826, 1832, 1838, 1844, 1849, 1855, 1860, 1865, 1870, 1874, 1879, 1884, 1888, 1892, 1897, 1901, 1905, 1908, 1912, 1916, 1919, 1923, 1926, 1929, 1933, 1936, 1939, 1942, 1944, 1947, 1950, 1953, 1955, 1958, 1960, 1962, 1965, 1967, 1969, 1971, 1973, 1975, 1977, 1979, 1981, 1983, 1985, 1986, 1988, 1989, 1991, 1993, 1994, 1996, 1997, 1998, 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2018, 2019, 2020, 2021, 2021, 2022, 2023, 2023, 2024, 2025, 2025, 2026, 2027, 2027, 2028, 2028, 2029, 2029, 2030, 2030, 2031, 2031, 2032, 2032, 2033, 2033, 2033, 2034, 2034, 2035, 2035, 2035, 2036, 2036, 2036, 2037, 2037, 2037, 2037, 2038, 2038, 2038, 2039, 2039, 2039, 2039, 2040, 2040, 2040, 2040, 2040, 2041, 2041, 2041, 2041, 2041, 2042, 2042, 2042, 2042, 2042, 2042, 2043, 2043, 2043, 2043, 2043, 2043, 2043, 2043, 2044, 2044, 2044, 2044, 2044, 2044, 2044, 2044, 2044, 2045, 2045, 2045, 2045, 2045, 2045, 2045, 2045, 2045, 2045, 2045, 2045, 2046, 2046, 2046, 2046, 2046, 2046, 2046, 2046, 2046, 2046

Then to grab a number in my update code:

updateFreq12:
   
    ld hl, expoTable
    ld a, [AUD1freq_RAM] ; the 8-bit number from 0-255; located at $D00x for the record
    ld l,a
    ld a,[hl+] ; A reg has high 3 bits. HL points to lower 8 now
    ld [rAUD1HIGH],a
    ld a,[hl]
    ld [rAUD1LOW],a
   
ret

Alternatively, I can get the 16-bit number from my table and shift the top 3 bits into AUD1HIGH, then load the remaining number into AUD1LOW. This process more closesly answers my original question. tongue
I'll code it up and see which compiles to the fewest lines.

Thanks so much for your help, guys!

Last edited by Orgia Mode (Oct 25, 2018 9:03 pm)

Offline
Nomad's Land

Save 4 cycles: ld hl, expoTable -> ld h,expoTable>>8

Offline
Sea of Souls

NICE!! Thats 4-cycles per CALL and I actually call it several times in the full loop.

Hey hey, how did you calculate the cycles saved?

Last edited by Orgia Mode (Oct 25, 2018 10:32 pm)

Offline
Nomad's Land

You need an opcode table that lists instruction timings. I used this one: http://www.pastraiser.com/cpu/gameboy/g … codes.html
Not the best but it does the job. The timings are listed below the opcode names; the number to the left is the instruction size in bytes, and the one to the right is the cycle count.

Offline
Sea of Souls
irrlichtproject wrote:

You need an opcode table that lists instruction timings. I used this one: http://www.pastraiser.com/cpu/gameboy/g … codes.html
Not the best but it does the job. The timings are listed below the opcode names; the number to the left is the instruction size in bytes, and the one to the right is the cycle count.

That is very helpful. I think I came across that page ears ago, but didn't need it at the time. Thanks!

Can anyone elaborate on these bullet points:
source

>>If the wave channel is enabled, accessing any byte from $FF30-$FF3F is equivalent to accessing the current byte selected by the waveform position. Further, on the DMG accesses will only work in this manner if made within a couple of clocks of the wave channel accessing wave RAM; if made at any other time, reads return $FF and writes have no effect.

>>Triggering the wave channel on the DMG while it reads a sample byte will alter the first four bytes of wave RAM. If the channel was reading one of the first four bytes, the only first byte will be rewritten with the byte being read. If the channel was reading one of the later 12 bytes, the first FOUR bytes of wave RAM will be rewritten with the four aligned bytes that the read was from (bytes 4-7, 8-11, or 12-15); for example if it were reading byte 9 when it was retriggered, the first four bytes would be rewritten with the contents of bytes 8-11. To avoid this corruption you should stop the wave by writing 0 then $80 to NR30 before triggering it again. The game Duck Tales encounters this issue part way through most songs.

Offline
Sea of Souls
Orgia Mode wrote:
irrlichtproject wrote:

You need an opcode table that lists instruction timings. I used this one: http://www.pastraiser.com/cpu/gameboy/g … codes.html
Not the best but it does the job. The timings are listed below the opcode names; the number to the left is the instruction size in bytes, and the one to the right is the cycle count.

That is very helpful. I think I came across that page ears ago, but didn't need it at the time. Thanks!

Can anyone elaborate on these bullet points:
source

>>If the wave channel is enabled, accessing any byte from $FF30-$FF3F is equivalent to accessing the current byte selected by the waveform position. Further, on the DMG accesses will only work in this manner if made within a couple of clocks of the wave channel accessing wave RAM; if made at any other time, reads return $FF and writes have no effect.

>>Triggering the wave channel on the DMG while it reads a sample byte will alter the first four bytes of wave RAM. If the channel was reading one of the first four bytes, the only first byte will be rewritten with the byte being read. If the channel was reading one of the later 12 bytes, the first FOUR bytes of wave RAM will be rewritten with the four aligned bytes that the read was from (bytes 4-7, 8-11, or 12-15); for example if it were reading byte 9 when it was retriggered, the first four bytes would be rewritten with the contents of bytes 8-11. To avoid this corruption you should stop the wave by writing 0 then $80 to NR30 before triggering it again. The game Duck Tales encounters this issue part way through most songs.


Alright, I think I figured it out.
When I reload $FF30 - $FF3F, have to turn it off, then load it and then re-enable it and then re-start the wave form, like so:


updateWaveSample:
; check if a new sample was selected, other wise skip it.
.tstOldWave:
    ld a, [AUD3wave_RAM]
    ld hl, AUD3wave_RAMold
    cp [hl]
    jp z, .noWaveUpdate ; skip if they are exactly the same
; otherwise, choose which sample to load
    ld h, waveTables>>8 ; its currently at $3200
    ld a, [AUD3wave_RAM] ; $00-$FF
    and %11110000 ; clear lower 4-bits, leaving 16 values for the user to select.
    ld l,a ; hl now points to the selected sample.
    ld a,$30
    ld c,a    ; C is the counter
    xor a
    ld [rAUD3ENA],a ; disable the wave channel here... test
.waveLoad:
    ld a,[hl+] ; load the first byte sample, then increment the pointer
    ld [c],a ; Put A into address $FF00 + C
    inc bc ; c = $30,31 ... 3F
    ld a,c
    cp ($3F+1)
    jp nz,.waveLoad
   
    ld a, $80
    ld [rAUD3ENA],a ; re-enable the wave channel here... test
    ; also need to re-start the wave channel with rAUD3HIGH bit 7
    ; get the current upper 3-bits of the frequency again. (it cannot simply be read from the register.)
    ld h, expoTable>>8
    ld a, [AUD3freq_RAM]
    ld l,a
    ld a,[hl] ; A reg has high 3 bits.
    set 7,a ; when set, sound restarts.
    ld [rAUD3HIGH],a
   
   
; waveLoading finished when $3F is loaded
    ld a, [AUD3wave_RAM]
    ld [AUD3wave_RAMold],a ; update the old user's input for wave ram selection
.noWaveUpdate:


; other irrelevant stuff removed for brevity

ret

it seems to test correctly in several emulators, simulating a variety of CPU's.

Offline
Nomad's Land

Another small optimization. Instead of

    ld a,[hl] ; A reg has high 3 bits.
    set 7,a ; when set, sound restarts.

you can do

   ld a,$80
   or [hl]

to save another 4 cycles wink
If you reorganize the whole block to set HL first, you can save another 8 cycles because you'll need to set A=$80 only once.

Last edited by irrlichtproject (Oct 27, 2018 12:26 am)