Retrocomputing Asked by SF. on January 21, 2021
Oktalyzer was a Tracker style program for Amiga, that allowed composing and playing music with 8 channels instead of the “hardware-natural” 4.
So far I learned it achieved that by splitting the 4 8-bit channels into 8 4-bit ones. But I still don’t understand how it was done from ‘mathematics’ point of view – it doesn’t seem like you can split a DAC index into two halves and have two waveforms of comparable volume overlaid.
One would range 0-16 in 1-bit increments and the other 0-256 in 16-bit increments, resulting in one being 16 times more quiet than the other. Or you could take every other bit, achieving 16-bit increments on both, but one would still be half as loud as the other, not having access to the most significant bit. How was it done in reality?
According to the very same link you provided:
To perform this feat, Oktalyzer loaded eight channels in memory, mixed them in real time down to four channels, and sent the result to the Amiga sound chip. This was a processor-intensive task which degraded sound quality, but was more than made up for with doubled channels. Oktalyzer could also be run in 4-channel mode to suit more processor-heavy programs.
which makes more sense to me that splitting each channel into two 4-bit channels.
So the process is quite straightforward. During the time between two VBlank interrupts (these occurs normally each 20 ms), the CPU has to take two sample buffers, mix them and write the result to a channel buffer that will be used to feed Paula.
This is quite processor intensive, as for each sample in a 20 ms buffer, the CPU has to calculate what sample to fetch (depending upon the playing frequency), check if it reached the end of sample so it will start at repetition begin. With both samples gathered, each one has to be scaled according to its volume setting, then the CPU has to add them and possibly, apply some compression to avoid clipping while keeping the original dynamic range (adding both sample and then dividing into 2 would result in too quiet sound)
I've just digged into the source code of an early release of Oktalyzer and found this macro: (comments of my own)
GetSample: macro
move.b (a0,d0.w),d4 ;get sample from buffer addressed by a0
add.b (a1,d1.w),d4 ;add sample from buffer addressed by a1
eor.b d5,d4 ;d5=-128. Fast way to add -128 to result
move.b d4,(a5)+ ;store result into output buffer
swap d0
swap d1
add.l d2,d0 ;calculate offset for next sample, buffer a0
add.l d3,d1 ;ditto for buffer a1
swap d0
swap d1
endm
Which pretty does much of what I've explained (minus the volume scaling operation).
From this source code, I guess that initial buffers pointed by a0
and a1
already contain scaled samples, as there is no code in the macro to check for clipping. I also assume that these original samples are 7 bits unsigned samples. Adding them won't produce clipping, but we will end up with a 8-bit unsiged sample, which is not suitable for Paula, which expects 8-bit signed samples. To get that, we have to substract 128 from the sample value, that's to say, add -128. Adding -128 to a 8-bit number is just toggling its MSb, so d5 is preloaded with -128 and then it is XORed with the calculated sample.
Correct answer by mcleod_ideafix on January 21, 2021
Since I can't comment, I thought I'd add to mcleod_ideafix's answer and explain how pitch is handled with the existing code -- it's using 16.16 fixed point arithmetic. So, if you wanted the loop to increment one address per loop; you'd provide a value of $00010000, but if you wanted to halve the frequency, you'd provice $00008000. What's happening here is that the high word will now only increment every other loop. We then simply SWAP the high and low words to use the high portion in our address offset.
; code enters with d2 and d3 holding the period counters in 16.16 notation
; a0 and a1 point to our sample addresses and d0/d1 should be zero
GetSample: macro
move.b (a0,d0.w),d4 ; get sample from buffer addressed by a0 + d0
add.b (a1,d1.w),d4 ; add sample from buffer addressed by a1 + d1
eor.b d5,d4 ; d5=-128. Fast way to add -128 to result
move.b d4,(a5)+ ; store result into output buffer
swap d0 ; restore d0/d1 to normal 16.16 format
swap d1 ; (high in high half, low in low half)
add.l d2,d0 ; calculate offset for next samples
add.l d3,d1 ; 16.16 addition is the same as 32.0
swap d0 ; put the high word back into the low half
swap d1 ; for each index (this is effectively a /65536)
endm
Answered by Renee Cousins on January 21, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP