# Using Pulse Density Modulation for 8-bit PCM

## Recommended Posts

Great idea! Works like a charm:

Could you elaborate on the 64kHz 1/3 carrier idea?

You're generating 1/16 duty cycle square wave with different amplitudes to set sublevel between 0/16 (\$e0 -> AUDC1) to 15/16 (\$ef -> AUDC1) - similar effect you could achieve by making PWM with volume 1 and duty cycle 0..15 (assuming 16-cycle length square wave). At first case field of impulse is controlled vertically, at the second case - horizontally.

Yours 1/16 duty-cycle square wave has 16 cycles period and runs with 1773447/16 Hz (~110kHz) so it's in inaudible and you can control volume by full 16 values (that's main reason to set 16-cycles square wave period).

Unfortunatelly channels 2 and 4 can't work with 1.77MHz but only with 64kHz (1773447/28=63337.3928 Hz) so we can't set square wave to 16-cycles length because it would be audible (1773447/28/16=3958.5870 Hz). You can set 1/3 square wave the same way as for channels 1+3 (store AUDF2, AUDF4, STIMER, AUDF4) so carrier wave will runs with 1773447/28/3=21112.4643 Hz and should be inaudible (your pets will leave room at most), but you can still control amplitude of this 1/3 square wave (in range 0..2) to achieve audio membrane sublevels.

Because you can use only 3 values different table should be calculated for sample:

round((sample & \$F) * 2 / \$F) | \$e0

\$F is max value of low nibble and 2 is max value of duty cycle.

Real sample resolution theoretically should be 5.585-bit (log(16*3,2)) (16*3, because high nibble of sample set directly in AUDC4 gives us 4-bit resolution).

You can try with 1/4 duty-cycle (and reach 6-bit sample resolution (log(16*4,2))) but carrier wave frequency will be 1773447/28/4=15834.3482 Hz so it could be audible (you will leave room together with your pets). Table can be calculated then:

round((sample & \$F) * 3 / \$F) | \$e0

Thanks for your experiments because they made me able to understand how this trick works . I hope I described my thoughts clearly.

Edited by mono

##### Share on other sites

You're generating 1/16 duty cycle square wave with different amplitudes to set sublevel between 0/16 (\$e0 -> AUDC1) to 15/16 (\$ef -> AUDC1) - similar effect you could achieve by

making PWM with volume 1 and duty cycle 0..15 (assuming 16-cycle length square wave). At first case field of impulse is controlled vertically, at the second case - horizontally.

Yours 1/16 duty-cycle square wave has 16 cycles period and runs with 1773447/16 Hz (~110kHz) so it's in inaudible and you can control volume by full 16 values (that's main reason to set 16-cycles square wave period).

Unfortunatelly channels 2 and 4 can't work with 1.77MHz but only with 64kHz (1773447/28=63337.3928 Hz) so we can't set square wave to 16-cycles length because it would be audible (1773447/28/16=3958.5870 Hz). You can set 1/3 square wave the same way as for channels 1+3 (store AUDF2, AUDF4, STIMER, AUDF4) so carrier wave will runs with 1773447/28/3=21112.4643 Hz and should be inaudible (your pets will leave room at most), but you can still control amplitude of this 1/3 square wave (in range 0..2) to achieve audio membrane sublevels.

Because you can use only 3 values different table should be calculated for sample:

round((sample & \$F) * 2 / \$F) | \$e0

\$F is max value of low nibble and 2 is max value of duty cycle.

Real sample resolution theoretically should be 5.585-bit (log(16*3,2)) (16*3, because high nibble of sample set directly in AUDC4 gives us 4-bit resolution).

You can try with 1/4 duty-cycle (and reach 6-bit sample resolution (log(16*4,2))) but carrier wave frequency will be 1773447/28/4=15834.3482 Hz so it could be audible (you will leave room together with your pets). Table can be calculated then:

round((sample & \$F) * 3 / \$F) | \$e0

Thanks for your experiments because they made me able to understand how this trick works . I hope I described my thoughts clearly.

I'm not a fan of any of the PWM examples posted. It's a cool trick, but regardless of frequency used, I always hear nasty background squeal. Even the 31kHz examples recently posted by NRV. I obviously cannot hear anywhere close to 31kHz, but I (as well as my audio spectrum analyzers) show terrible hi-freq noise.

##### Share on other sites

I'm not a fan of any of the PWM examples posted. It's a cool trick, but regardless of frequency used, I always hear nasty background squeal. Even the 31kHz examples recently posted by NRV. I obviously cannot hear anywhere close to 31kHz, but I (as well as my audio spectrum analyzers) show terrible hi-freq noise.

Interesting.

And, well, 31kHz means 15.5kHz sampling exactness. And , yes, if you use some silly device without any linearity, you can hear the high frequency "squeal".

But, what makes me even more wondering, Why do you like POKEY tunes that squeal by standard?

If we can solve this "issue", I probably will get what's going on

##### Share on other sites

Interesting.

And, well, 31kHz means 15.5kHz sampling exactness. And , yes, if you use some silly device without any linearity, you can hear the high frequency "squeal".

But, what makes me even more wondering, Why do you like POKEY tunes that squeal by standard?

If we can solve this "issue", I probably will get what's going on

I don't use "silly devices". My small room setup is a Sansui 7070 receiver through a BSR EQ-3000 and wall mounted dual 6.5" studio monitors (12' separation). My full media-room setup is using several killowatts of rack mounted Crown amplifiers from the 70s/80s. Front and rears play through 31 band EQs, and the room has 6 12" woofers, 4 10" woofers, and numerous mids and tweets. You don't have to explain audio to me.

I'm very sorry if only I can hear this - but my scope, and 4 separate equalizers / spectrum analyzers, and my ears all agree. I don't know what else you want me to say.

Also, major difference between out of tune pokey squealing, and carrier frequency given by PWM (and also proven mathematically, not just by what my ears hear).

##### Share on other sites

Here's the sine wave on the 130XE audio output with the calibration program running on default settings (12/13):

...and here it is with 11/13:

So yeah, we clearly need an extra cycle of delay between the two. But even then, the large and small waves don't match up quite right. The sawtooth shows this more clearly:

And here's a sweep of the LSB-only sawtooth, which is very noisy but is on average what we're looking for:

The large-scale and small-scale waves are clearly being added together as expected, but the slopes aren't matching up quite right. You can also see the nonlinearity in POKEY's output, which also complicates things.

##### Share on other sites

The best results I could get were with X=3, Y=5. Still have a bit of nonlinearity in the scale, but dunno how much you'd actually hear it.

• 14

##### Share on other sites

I don't use "silly devices". My small room setup is a Sansui 7070 receiver through a BSR EQ-3000 and wall mounted dual 6.5" studio monitors (12' separation). My full media-room setup is using several killowatts of rack mounted Crown amplifiers from the 70s/80s. Front and rears play through 31 band EQs, and the room has 6 12" woofers, 4 10" woofers, and numerous mids and tweets. You don't have to explain audio to me.

That explains much. Back in the 80s people were happy to have setups that play a huge frequency range. The prior technology was rather limited. So they built speakers with all open for high frequencies. Later particular the "monitor" speakers still used harsh speakers for high sounds. It's also a physical rule that high tones lose loudness directly by the distance to the speakers, while low tones get even louder. Approximately 7-20 meters by the wave length.

So in a small rooms such speakers never get their linear range. High tones get louder for up to 10dB with ease, while deep basses were fully cancelled, just "resulting waves" occur.

Edited by emkay

##### Share on other sites

That explains much. Back in the 80s people were happy to have setups that play a huge frequency range. The prior technology was rather limited. So they built speakers with all open for high frequencies. Later particular the "monitor" speakers still used harsh speakers for high sounds. It's also a physical rule that high tones lose loudness directly by the distance to the speakers, while low tones get even louder. Approximately 7-20 meters by the wave length.

So in a small rooms such speakers never get their linear range. High tones get louder for up to 10dB with ease, while deep basses were fully cancelled, just "resulting waves" occur.

Shall I now mention my spectrum analyzers also have calibrated microphones and pink noise generators so I can get perfectly flat response (if I desire it) in my listening areas?

##### Share on other sites

Shall I now mention my spectrum analyzers also have calibrated microphones and pink noise generators so I can get perfectly flat response (if I desire it) in my listening areas?

If you say so.

##### Share on other sites

Shall I now mention my spectrum analyzers also have calibrated microphones and pink noise generators so I can get perfectly flat response (if I desire it) in my listening areas?

emkay might not be impressed, or even believe you, but I thought I'd chime in and say I'd love to see that studio of yours .

Sounds like something I would've drooled over back in my younger days.

##### Share on other sites

emkay might not be impressed, or even believe you, but I thought I'd chime in and say I'd love to see that studio of yours .

Sounds like something I would've drooled over back in my younger days.

I'll shoot you a PM so I don't clog the thread any further. I've been into audio almost as long as I have been into Atari

##### Share on other sites

The best results I could get were with X=3, Y=5. Still have a bit of nonlinearity in the scale, but dunno how much you'd actually hear it.

This looks really clean! So, this is a 130XE? I wonder if we need different calibration values for different models or different POKEY chips?

So, do you think those settings are creating a 1/7 duty cycle signal? AUDF1 should be pulsing every 7 cycles with a setting of X=3, and I guess we're thinking now that the AUDF3 high-pass sampler should be offset by 1 cycle relative to AUDF1 with an initial setting of Y=5 and then going back to 3 after one pulse?

If POKEY's drivers that generate the waveform have asymmetric rise/fall times where rise time is slower than fall time, then the analog output would have a smaller pulse width than what the pure digital signal would suggest. Certainly such an effect would be more pronounced at 1.79MHz. Then it would stand to reason that we would need to run digitally at a duty cycle larger than 1/16 in order to get close to a 1/16 duty cycle on the analog output. I wonder if other duty cycles near 1/7 would also be potential candidates, like 2/13, 2/15, 3/20 3/22, etc. I need to buy a scope and pull out my Ataris if I ever get some free time.

##### Share on other sites

Here's an XEX that uses 1088MB of RAM:

ram1088.xex

You can press START during playback to switch between 1/16 duty cycle tuned for Altirra (white on black background) and 1/7 duty cycle tuned for phaeron's 130XE (black on white background). Maybe some folks can run on real hardware and verify that it sounds good in the black on white background mode?

##### Share on other sites

Thanks for that: been dying to give this a try on real hardware but hadn't gotten around to testing the ROM builds on the Ultimate Cart. Here's the XEX running on my 600XL:

Sorry about the low sound, but you can hear that white on black is extremely hissy. Black on white sounds good to me, though.

##### Share on other sites

Thanks for that: been dying to give this a try on real hardware but hadn't gotten around to testing the ROM builds on the Ultimate Cart. Here's the XEX running on my 600XL:

Sorry about the low sound, but you can hear that white on black is extremely hissy. Black on white sounds good to me, though.

Awesome! So, we have two models, 130XE and 600XL that seem to work well with the same settings. That bodes well for a generalized solution. Would love to see what we get with an Incognito 800.

##### Share on other sites

Would love to see what we get with an Incognito 800.

OK: give me twenty minutes or so and I will report back.

##### Share on other sites

Hmmm... interesting results on the 800. Same settings (predictably) work best on that model, but the tune skips and jumps badly, like a stuck record. CPU or NMI differences? Incognito was in XL/XE mode and I verified that it wasn't the loader's fault by loading the XEX via RespeQt with the exact same result.

Generally consistent result, nevertheless.

##### Share on other sites

Hmmm... interesting results on the 800. Same settings (predictably) work best on that model, but the tune skips and jumps badly, like a stuck record. CPU or NMI differences? Incognito was in XL/XE mode and I verified that it wasn't the loader's fault by loading the XEX via RespeQt with the exact same result.

Generally consistent result, nevertheless.

Excellent! So maybe it's a function of POKEY itself and not the motherboard audio circuitry or else the audio circuitry is sufficiently similar among models.

If it's skipping around it could be you have it in 320MB or 576MB mode instead of 1088MB mode.

##### Share on other sites

Excellent! So maybe it's a function of POKEY itself and not the motherboard audio circuitry or else the audio circuitry is sufficiently similar among models.

It would seem so, although I should point out that the 600XL has a stereo board.

If it's skipping around it could be you have it in 320MB or 576MB mode instead of 1088MB mode.

Nope: I made sure not to make that kind of silly mistake. 1088KB, and loaded from the FAT first time around using the XEX loader, then over SIO.

Edited by flashjazzcat

##### Share on other sites

Excellent! So maybe it's a function of POKEY itself and not the motherboard audio circuitry or else the audio circuitry is sufficiently similar among models.

It would seem so, although I should point out that the 600XL has a stereo board.

If it's skipping around it could be you have it in 320MB or 576MB mode instead of 1088MB mode.

Nope: I made sure not to make that kind of silly mistake. 1088KB, and loaded from the FAT first time around using the XEX loader, then over SIO.

Duly noted about the stereo board. So we have the following list of working models:

• 130XE
• 600XL with stereo board
• Incognito 800

Looks like I need to research how PORTB works on Incognito. I know there are some order-of-operations considerations for 1088MB mode that I may need to take into account.

##### Share on other sites

Testing on my 1088XEL now. Should have put on my HDD. Takes forever to dump a MB of data even at 3X SIO

##### Share on other sites

Looks like I need to research how PORTB works on Incognito. I know there are some order-of-operations considerations for 1088MB mode that I may need to take into account.

You think you may be doubling up on a few banks?

Testing on my 1088XEL now. Should have put on my HDD. Takes forever to dump a MB of data even at 3X SIO

##### Share on other sites

IMHO it would behave differently at different frequencies. If you want 1/16 pulse, you want 8 times higher frequency, just with 7 from the 8 pulses being suppressed.

I guess it must the analog circuitry not catching up. Maybe try reading the signal with oscilloscope right of the POKEY pin.

##### Share on other sites

You think you may be doubling up on a few banks?

Yeah - it's on the loader now. Edited my previous post, video is now online.

##### Share on other sites

Testing on my 1088XEL now. Should have put on my HDD. Takes forever to dump a MB of data even at 3X SIO

Awesome! So we can add 1088XEL to the list. BTW, I think you mean volume alert at 0.05.

## Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

×   Pasted as rich text.   Paste as plain text instead

Only 75 emoji are allowed.

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.