VOGONS


CMS/GameBlaster emulation thread

Topic actions

Reply 40 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

I just took a little crack at trying to implement the PDM replacement for PWM. The output doesn't seem to match though(it might still have some basic signal processing problem).

byte WAVEFORM_OUTPUT[16][16] = { //PDM Waveforms for a selected output!
#ifndef PDM_OUTPUT
//PWM output?
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, //Volume 0
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, //Volume 1
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, //Volume 2
{1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0}, //Volume 3
{1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0}, //Volume 4
{1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0}, //Volume 5
{1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0}, //Volume 6
{1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0}, //Volume 7
{1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0}, //Volume 8
{1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0}, //Volume 9
{1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0}, //Volume A
{1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0}, //Volume B
{1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0}, //Volume C
{1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0}, //Volume D
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0}, //Volume E
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0} //Volume F
#else
//PDM output?
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, //Volume 0 Same as PWM
{0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0}, //Volume 1 TODO(+2)
{0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0}, //Volume 2 TODO(+3)
{0,0,0,0,1,1,0,0,0,0,0,0,1,1,1,0}, //Volume 3 TODO(+5)
{0,0,0,0,1,1,1,0,0,0,0,0,1,1,1,0}, //Volume 4 TODO(+6)
{0,0,0,0,1,1,1,0,0,0,0,0,1,1,1,1}, //Volume 5 TODO(+7)
{0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1}, //Volume 6 TODO(+8)
{1,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1}, //Volume 7 1H 3L 4H 4L 4H confirmed(+9)
{1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0}, //Volume 8 4H 4L 4H 4L confirmed(+8)
{1,1,1,1,0,1,0,0,1,1,1,1,0,0,0,0}, //Volume 9 4H 4L 4H 4L(+9)
{1,1,1,1,0,1,0,0,1,1,1,1,0,1,0,0}, //Volume A TODO(+10)
{1,1,1,1,0,1,1,0,1,1,1,1,0,1,0,0}, //Volume B TODO(+11)
{1,1,1,1,0,1,1,0,1,1,1,1,0,1,1,0}, //Volume C TODO(+12)
{1,1,1,1,0,1,1,0,1,1,1,1,0,1,1,1}, //Volume D TODO(+13)
{1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1}, //Volume E TODO(+15)
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0} //Volume F Same as PWM
#endif
};

Have I done this correctly? Volume 8&7 are taken directly from your comments. The others I've tried to average roughly myself(I hope I did it correcly, trying to keep in mind to make the ones count a bit more than the zeroes)). Is this somewhat in the right direction?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 41 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've enabled the DEBUG_OUTPUT define to make the Game Blaster generate a test signal(440Hz tone+noise), manually setting the square wave settings for a testcase.

	#ifdef DEBUG_OUTPUT
//manually set a test frequency!
GAMEBLASTER.chips[0].squarewave[0].timeout = (uint_32)(__GAMEBLASTER_BASERATE/(double)(2.0*DEBUG_OUTPUT)); //New timeout!
GAMEBLASTER.chips[0].squarewave[0].timepoint = 0; //Reset!
GAMEBLASTER.chips[0].channels[0].freq = GAMEBLASTER.chips[0].squarewave[0].freq = DEBUG_OUTPUT; //We're updated!
GAMEBLASTER.chips[0].squarewave[8].timeout = (uint_32)(__GAMEBLASTER_BASERATE/(double)(2.0*DEBUG_OUTPUT)); //New timeout!
GAMEBLASTER.chips[0].squarewave[8].timepoint = 0; //Reset!
GAMEBLASTER.chips[0].squarewave[8].freq = DEBUG_OUTPUT; //We're updated!
outGameBlaster(GAMEBLASTER.baseaddr+1,0x00); //Channel 0 amplitude!
outGameBlaster(GAMEBLASTER.baseaddr,0xFF); //Maximum amplitude!
outGameBlaster(GAMEBLASTER.baseaddr+1,0x18); //Channel 0-3 settings!
outGameBlaster(GAMEBLASTER.baseaddr,0x82); //Enable frequency output at full volume!
outGameBlaster(GAMEBLASTER.baseaddr+1,0x1C); //General settings!
outGameBlaster(GAMEBLASTER.baseaddr,0x01); //Enable all outputs!
outGameBlaster(GAMEBLASTER.baseaddr+1,0x14); //Channel n frequency!
outGameBlaster(GAMEBLASTER.baseaddr,0x01); //Enable frequency output!
outGameBlaster(GAMEBLASTER.baseaddr+1,0x15); //Channel n frequency!
outGameBlaster(GAMEBLASTER.baseaddr,0x01); //Enable noise output!
outGameBlaster(GAMEBLASTER.baseaddr+1,0x16); //Channel n frequency!
outGameBlaster(GAMEBLASTER.baseaddr,0x03); //Set noise output mode!
#endif

This results in the following audio to be generated using the current formulas(based on the information implemented right now):

Filename
UniPCemu_testtone_440HzFrequency+Noise_DirectOutput_NoFiltering.zip
File size
572.46 KiB
Downloads
64 downloads
File comment
Resulting filtered audio.
File license
Fair use/fair dealing exception

Can you see if this output is correct Jepael? The PWM, as well as filtering is disabled in this testcase to provide a pure 44kHz unfiltered signal to test with(lowest-neighbor downsampled from 7MHz, no filtering applied nor PDM('Dosbox'-style output)).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 42 of 61, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie

Here's the amplitude "chopping" table, from volume 15 (max) down to 0 (min, off).
The phases should be aligned in respect to each other, but of course I have no idea which is the first phase in absolute terms.
H=square wave is high=current sink off=idle
L=square wave is low=current sink on=active

HLLL-LLLL-LLLL-LLLL
HHLL-LLLL-LLLL-LLLL
HLHH-LLLL-LLLL-LLLL
HHHH-LLLL-LLLL-LLLL
HLLL-LLLL-HHHH-LLLL
HHLL-LLLL-HHHH-LLLL
HLHH-LLLL-HHHH-LLLL
HHHH-LLLL-HHHH-LLLL
HLLL-HHHH-LLLL-HHHH
HHLL-HHHH-LLLL-HHHH
HLHH-HHHH-LLLL-HHHH
HHHH-HHHH-LLLL-HHHH
HLLL-HHHH-HHHH-HHHH
HHLL-HHHH-HHHH-HHHH
HLHH-HHHH-HHHH-HHHH
HHHH-HHHH-HHHH-HHHH

Reply 43 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've implemented the waveforms into the Game Blaster emulation. The sound is much softer than the old PWM method now.

Now I just need to find out what's going wrong with the noise(/+tone) mixing. Although this bug mighthave been there from the start. The waveforms(tone waveforms and PDM waveforms) are obviously working. It's when noise is used(mixed in) that things seem to go wrong?

Edit: Are you sure that the tone+noise switches between tone on even and noise on odd PWM samples? Either that mixing method is incorrect, or my noise polynomial is royally screwing up? Any way to verify this?

Edit: Also, one little thing to note about my emulation: it's PWM output is generating five states essentially: active positive(+5V, SHRT_MAX), active negative(-5V, SHRT_MIN), finished positive(0V), finished negative(0V) and idle(0V). The resulting PCM samples are stored in the PWM_outputs variable(in the order as given, with idle filling the last four slots(overruling the normal outputs to 0V). That's actually the only part not conforming to your documentation given about the sinks afaik. The PWM/PDM modulation works by switching between the even indexes(positive tone/noise use even input indexes 0(1)/2(0)) and negative tone/noise input use odd indexes(1(1)/3(0)), according to the PDM waveforms in your last post. Add four to the indexes for their muted variants(used for inactive sound channels with tone&frequency enable bits cleared(to not produce sound)).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 44 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

Btw, the table you gave, I assume it's ordered from least volume(HLLL-LLLL-LLLL-LLLL) to most volume(all H,meaning maximum voltage)? Cause that's the way I've implemented it.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 45 of 61, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

Btw, the table you gave, I assume it's ordered from least volume(HLLL-LLLL-LLLL-LLLL) to most volume(all H,meaning maximum voltage)? Cause that's the way I've implemented it.

No, exactly the other way around like I said in my post.

From volume 15 at the top to volume 0 at the bottom.

So maximum number of L's is strongest volume as that is the longest active pulse of 15 counts the waveform spends low, and maximum number of H's is zero volume as the waveform sits idle high all 16 counts.

Reply 46 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

So you mean the Hs and Ls essentially reversed on the PCM side of things? So the single H with lots of Ls following it resolve in maximum voltage due to being reversed on the output(the idling high part)? So HLLL-LLLL-LLLL-LLLL results in 0V on the first subsample and +/-5V on the other 15 subsamples? Thus being 0V the first sample only and maximum voltage on the rest of the time? That would explain why most Monkey Island music is overshadowed by loud background noise(inversed volume)?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 47 of 61, by Jepael

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

Edit: Are you sure that the tone+noise switches between tone on even and noise on odd PWM samples? Either that mixing method is incorrect, or my noise polynomial is royally screwing up? Any way to verify this?

I am not 100% sure about anything any more. Are you sure you alternate the amplitude periods of 16 samples per period, not each sample?

superfury wrote:

Edit: Also, one little thing to note about my emulation: it's PWM output is generating five states essentially:

I won't argue about what is the best way to actually emulate the output of a channel or the whole chip in most efficient way.
Square waves and other 1-bit signals still by definition have only two states.

Reply 48 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++
Jepael wrote:
I am not 100% sure about anything any more. Are you sure you alternate the amplitude periods of 16 samples per period, not each […]
Show full quote
superfury wrote:

Edit: Are you sure that the tone+noise switches between tone on even and noise on odd PWM samples? Either that mixing method is incorrect, or my noise polynomial is royally screwing up? Any way to verify this?

I am not 100% sure about anything any more. Are you sure you alternate the amplitude periods of 16 samples per period, not each sample?

superfury wrote:

Edit: Also, one little thing to note about my emulation: it's PWM output is generating five states essentially:

I won't argue about what is the best way to actually emulate the output of a channel or the whole chip in most efficient way.
Square waves and other 1-bit signals still by definition have only two states.

The PWM state is currently switched between the two(tone vs noise output) each time a PWM sample is rendered(iow: the entire 16 states of the table you gave has been rendered). This is done on the right channel(the last stereo channel to render it's PWM subsample).

The amplitude and note/noise/PWM period input IS reloaded each 3.57MHz sample, but only loaded into the PDM generator once it reaches subsample #0. It's ignored for subsamples 1-15, in order to create a stable PDM/PWM output.

Edit: Just for terminology, I use PWM sample to mean an output that resolves (using PWM) to a single sample(just like a PCM sample). With PWM subsample(for lack of an official word) I mean any of the 16 samples that, when low-pass filtered, combine into one full PCM sample. So to compare: On the PC speaker PWM(8088 MPH credits song), one PWM sample(6-bit) consists of 72 PWM subsamples.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 49 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

I just thought about something: during the noise+tone emulation case, noise and tone are interleaved, thus equal in strength(same amplitude). This would mean that the noise might have incorrect amplitude(dosbox has it halved, UniPCemu now doesn't). Suppose the noise is stronger now, strong enough to overpower the tone part. Now multiple of those cases would result in much too loud noise when done with multiple channels(and single in a slight case, double that of Dosbox)? Is that correct behaviour?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 50 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just a little question: should I modify the lookup table for PWM_outputs to use entries 2-3 as reversed entries 0-1, instead of 0V? So with negative output, it would get negative as 5V and positive as -5V(instead of 0V), while positive does the same, but in reverse? What should the last four (idle) entries be? All -5V(SHRT_MIN)?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 51 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've just thought about my last post again, but although it's correct for plain 16-level positive-only square waves(alternating between +5V and 0V with only amplitude on positive voltages, it goes wrong with the square wave supporting positive/negative output(extending positively/negatively(depending on input) from 0V). In this case, volume 8-15 will produce a slightly positive or negative wave, but amplitudes ~7-0 at any point will invert itself into the opposite sign instead, since the amplitude is now counted in the distance between positive and negative together, thus overflowing into the opposite sign.

Thus(assuming linear when filtered):
Amplitude 0=-XMaxV
Amplitude 1-8=-XMaxV-0V
Amplitude 9-15=0V-XMaxV
Amplitude 16=XMaxV

Thus for the low volume part of the wave, it's inverted on the opposite sign in the 0-8 cases. This will completely mess up the square wave at lower volumes(7/8 being softest(inverted), 0/15 being loudest(half volume).

So the output needs 3 stages to work properly in 16 PWM subsamples. That's why it need to toggle between +/-UMax(sign dependant on square wave/noise) and 0V(center/no output).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 52 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've just reverted it to sample to 0V instead of reversing the sign. Now, the sound is finally correct(or at least plausible)! 😁 Although tone+noise's noise samples are currently reduced in 0-15 volume to be halved (half amplitude).

Current source code: https://bitbucket.org/superfury/unipcemu/src/ … ter.c?at=master

A recording of the current output playing Monkey Island's intro theme mostly:
https://www.dropbox.com/s/6vxg867jr9n7wq7/rec … 8_1732.zip?dl=0

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 53 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

I did a little big of optimization (adding likely and unlikely and removing unused variables from the Game Blaster module). It's now running at 67% during Monkey Island, while up to 97% without using the Game Blaster 😀 Running nicely on x64 Windows 10 i7@4.0GHz 😀 The 32-bit version is slower though(understandable due to less filtering speed).

I've also switched the low-pass filter from the little-heavier(one if instruction to determine high/low-pass filter) to the lighter no-check low-pass filter variant, further speeding the whole thing up.

Full current working source code: https://bitbucket.org/superfury/unipcemu/src/ … ter.c?at=master

There's still the strange case of some higher tones seemingly being mixed in. The Scumm Bar soundtrack sounds like a ghost playing the tune together with the main ensamble.

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 54 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

I just did some more optimization(optimizing variables and lookup tables). Full emulation fully running at ~70% with XT NEC V30 emulation on the same PC as last post 😁
The used version was compiled on MinGW 64-bit to get the overall best speed(on the rendering side, not affecting captured output in any way).

The recording, including BIOS that's booting(Using some PC speaker beeps), as well as sound from the EMS driver counting memory(the rapid low tones at the start): https://www.dropbox.com/s/5uwgtdxr97qtwgq/rec … 9_1424.zip?dl=0

The source code used to generate the recording(using UniPCemu's normal audio recording feature to capture the wave file):
https://bitbucket.org/superfury/unipcemu/src/ … ter.c?at=master

Anyone? Jepael? Is it close to the real thing?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 55 of 61, by stripwax

User metadata
Rank Newbie
Rank
Newbie

Hi all - I just joined the forum but I was made aware of this thread a few months ago. I wrote SAASound which is the emulation library used by SimCoupe and a couple of other things. As guessed earlier in this thread, yes, when I wrote the emulation it was for a Sam Coupe, where the SAA is always clocked at 8Mhz
OPLx - I saw you sent a patch to Simon Owen to adjust the clocking of SAASound , I'm guessing it's related to the above? SAASound is now on github. I'm going to incorporate your changes (or something amounting to the same anyway).
I also just picked up a real SAA-1099 (just the chip, on ebay) , which I am currently underclocking to let me analyse the output directly (I don't have a fancy oscilloscope). I started on this last night... I don't think the above PDM tables are quite correct and I'll proved an update here shortly.
Lastly : is this the latest thread on this topic? It seems interest died off quite suddenly last year.
Hi and thanks!

Reply 56 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

Oddly enough, I didn't receive any response back then.

The emulation doesn't use said table at the last post of mine anymore(it still CAN use it by disabling the PDM_OUTPUT define flag at line 28). It uses Jepael's amplitude "chopping" table now.

Current source code for the SAA1099:
https://bitbucket.org/superfury/unipcemu/src/ … le-view-default

All clock timing is based on the 14.31818MHz in my emulation though, divided by four for a 3.xxxxxxMHz(MHZ14_BASETICK) signal and by 324(MHZ14_RENDERTICK) to generate a (downsampled using a low-pass filter and downsampling) signal for sampling rendering output at as close as possible signal for 44.1kHz(adjust said number accordingly for better sound quality on available systems). Both of those divisions run in parallel to each other(base generates raw SAA1099 samples at it's official frequency, render samples those at full frequency and outputs a ~44.1kHz signal for the actual sound renderer).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 57 of 61, by superfury

User metadata
Rank l33t++
Rank
l33t++

@stripwax: So the PDM tables provided by Jepael are incorrect?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 58 of 61, by OPLx

User metadata
Rank Member
Rank
Member
stripwax wrote:
Hi all - I just joined the forum but I was made aware of this thread a few months ago. I wrote SAASound which is the emulation l […]
Show full quote

Hi all - I just joined the forum but I was made aware of this thread a few months ago. I wrote SAASound which is the emulation library used by SimCoupe and a couple of other things. As guessed earlier in this thread, yes, when I wrote the emulation it was for a Sam Coupe, where the SAA is always clocked at 8Mhz
OPLx - I saw you sent a patch to Simon Owen to adjust the clocking of SAASound , I'm guessing it's related to the above? SAASound is now on github. I'm going to incorporate your changes (or something amounting to the same anyway).
I also just picked up a real SAA-1099 (just the chip, on ebay) , which I am currently underclocking to let me analyse the output directly (I don't have a fancy oscilloscope). I started on this last night... I don't think the above PDM tables are quite correct and I'll proved an update here shortly.
Lastly : is this the latest thread on this topic? It seems interest died off quite suddenly last year.
Hi and thanks!

Hi stripwax, the patch wasn't directly related. I had made while I was doing some tests on the SAA1099 it a long time ago before the source had originally showed up on Simon's GitHub. I did see the official Github for SAASound, but I've been a bit busy lately and wasn't able to re-submit the change. I'm looking forward to your updates though! 😀

Reply 59 of 61, by stripwax

User metadata
Rank Newbie
Rank
Newbie

Ok, so a bit of a braindump here:

Pulse Density modulation:
Rather than think of this as a table of 16-bit sequences, I think of
this as a table of 64-bit sequences. (Admittedly, each bit is padded out
four times, but it simplifies the explanation of envelopes below)

1. Amplitude control:

 0: 0000000000000000000000000000000000000000000000000000000000000000 = 0
1: 0000111100000000000000000000000000000000000000000000000000000000 = 4/64
2: 0000000011111111000000000000000000000000000000000000000000000000 = 8/64
3: 0000111111111111000000000000000000000000000000000000000000000000 = 12/64
4: 0000000000000000000000000000000011111111111111110000000000000000 = 16/64
5: 0000111100000000000000000000000011111111111111110000000000000000 = 20/64
6: 0000000011111111000000000000000011111111111111110000000000000000 = 24/64
7: 0000111111111111000000000000000011111111111111110000000000000000 = 28/64
8: 0000000000000000111111111111111100000000000000001111111111111111 = 32/64
9: 0000111100000000111111111111111100000000000000001111111111111111 = 36/64
10: 0000000011111111111111111111111100000000000000001111111111111111 = 40/64
11: 0000111111111111111111111111111100000000000000001111111111111111 = 44/64
12: 0000000000000000111111111111111111111111111111111111111111111111 = 48/64
13: 0000111100000000111111111111111111111111111111111111111111111111 = 52/64
14: 0000000011111111111111111111111111111111111111111111111111111111 = 56/64
15: 0000111111111111111111111111111111111111111111111111111111111111 = 60/64

2. Envelope control
(4bit range on env) with amplitude=15
I took some measurements of each of the 16 envelope steps (using manual clocking)
with the channel Amplitude set to 15. Initially it was hard to tell exactly where
each sequence starts/ends - I attemped to align these sequences to best make sense
to me from the kinds of patterns you might generate by minimal AND gates plus
binary counters

 0: 0000000000000000000000000000000000000000000000000000000000000000 = 0/64
1: 0000000010000000000000001000000000000000100000000000000010000000 = 4/64
2: 0000000001000000010000000100000001000000010000000100000001000000 = 7/64
3: 0000000011000000010000001100000001000000110000000100000011000000 = 11/64
4: 0000000000000011000000110000001100000011000000110000001100000011 = 14/64
5: 0000000010000011000000111000001100000011100000110000001110000011 = 18/64
6: 0000000001000011010000110100001101000011010000110100001101000011 = 21/64
7: 0000000011000011010000111100001101000011110000110100001111000011 = 25/64
8: 0000000000111100001111000011110000111100001111000011110000111100 = 28/64
9: 0000000010111100001111001011110000111100101111000011110010111100 = 32/64
10: 0000000001111100011111000111110001111100011111000111110001111100 = 35/64
11: 0000000011111100011111001111110001111100111111000111110011111100 = 39/64
12: 0000000000111111001111110011111100111111001111110011111100111111 = 42/64
13: 0000000010111111001111111011111100111111101111110011111110111111 = 46/64
14: 0000000001111111011111110111111101111111011111110111111101111111 = 49/64
15: 0000000011111111011111111111111101111111111111110111111111111111 = 53/64

I then assumed that the resulting sequences above are actually just the
logical-AND operation of (env_pattern & amp_pattern). So the env_pattern
is probably actually just this below (repeating 4x in a 64-clock cycle)

 0: 0000000000000000 = 0/64
1: 0000000010000000 = 4/64
2: 0100000001000000 = 8/64
3: 0100000011000000 = 12/64
4: 0000001100000011 = 16/64
5: 0000001110000011 = 20/64
6: 0100001101000011 = 24/64
7: 0100001111000011 = 28/64
8: 0011110000111100 = 32/64
9: 0011110010111100 = 36/64
10: 0111110001111100 = 40/64
11: 0111110011111100 = 44/64
12: 0011111100111111 = 48/64
13: 0011111110111111 = 52/64
14: 0111111101111111 = 56/64
15: 0111111111111111 = 60/64

To confirm this, I rigged up a test with the amplitude set to 10 instead of 15,
and reran the outputs for each of the 16 envelope steps. I checked each one
and the patterns matched exactly.

amp volume = 10, env volume = 15
0000000011111111111111111111111100000000000000001111111111111111 MASK (i.e. amplitude PDM)
0111111111111111011111111111111101111111111111110111111111111111 ENV PDM
0000000001111111011111111111111100000000000000000111111111111111 = prediction (= amp & env)

amp volume = 10, env volume = 14
0000000011111111111111111111111100000000000000001111111111111111 MASK
0111111101111111011111110111111101111111011111110111111101111111 ENV
0000000001111111011111110111111100000000000000000111111101111111 = prediction

amp volume = 10, env volume = 13
0000000011111111111111111111111100000000000000001111111111111111 MASK
0011111110111111001111111011111100111111101111110011111110111111 ENV
0000000010111111001111111011111100000000000000000011111110111111 = prediction

amp volume = 10, env volume = 12
0000000011111111111111111111111100000000000000001111111111111111 MASK
0011111100111111001111110011111100111111001111110011111100111111 ENV
0000000000111111001111110011111100000000000000000011111100111111 = prediction

amp volume = 10, env volume = 11
0000000011111111111111111111111100000000000000001111111111111111 MASK
0111110011111100011111001111110001111100111111000111110011111100 ENV
0000000011111100011111001111110000000000000000000111110011111100 = prediction

amp volume = 10, env volume = 10
0000000011111111111111111111111100000000000000001111111111111111 MASK
0111110001111100011111000111110001111100011111000111110001111100 ENV
0000000001111100011111000111110000000000000000000111110001111100 = prediction

amp volume = 10, env volume = 9
0000000011111111111111111111111100000000000000001111111111111111 MASK
0011110010111100001111001011110000111100101111000011110010111100 ENV
0000000010111100001111001011110000000000000000000011110010111100 = prediction

amp volume = 10, env volume = 8
0000000011111111111111111111111100000000000000001111111111111111 MASK
0011110000111100001111000011110000111100001111000011110000111100 ENV
0000000000111100001111000011110000000000000000000011110000111100 = prediction

amp volume = 10, env volume = 7
0000000011111111111111111111111100000000000000001111111111111111 MASK
0100001111000011010000111100001101000011110000110100001111000011 ENV
0000000011000011010000111100001100000000000000000100001111000011 = prediction

amp volume = 10, env volume = 6
0000000011111111111111111111111100000000000000001111111111111111 MASK
0100001101000011010000110100001101000011010000110100001101000011 ENV
0000000001000011010000110100001100000000000000000100001101000011 = prediction

amp volume = 10, env volume = 5
0000000011111111111111111111111100000000000000001111111111111111 MASK
0000001110000011000000111000001100000011100000110000001110000011 ENV
0000000010000011000000111000001100000000000000000000001110000011 = prediction

amp volume = 10, env volume = 4
0000000011111111111111111111111100000000000000001111111111111111 MASK
0000001100000011000000110000001100000011000000110000001100000011 ENV
0000000000000011000000110000001100000000000000000000001100000011 = prediction

Show last 21 lines
amp volume = 10, env volume = 3
0000000011111111111111111111111100000000000000001111111111111111 MASK
0100000011000000010000001100000001000000110000000100000011000000 ENV
0000000011000000010000001100000000000000000000000100000011000000 = prediction

amp volume = 10, env volume = 2
0000000011111111111111111111111100000000000000001111111111111111 MASK
0100000001000000010000000100000001000000010000000100000001000000 ENV
0000000001000000010000000100000000000000000000000100000001000000 = prediction

amp volume = 10, env volume = 1
0000000011111111111111111111111100000000000000001111111111111111 MASK
0000000010000000000000001000000000000000100000000000000010000000 ENV
0000000010000000000000001000000000000000000000000000000010000000 = prediction

amp volume = 10, env volume = 0
0000000011111111111111111111111100000000000000001111111111111111 MASK
0000000000000000000000000000000000000000000000000000000000000000 ENV
0000000000000000000000000000000000000000000000000000000000000000 = prediction

I did the same with the 3-bit envelope mode (instead of the 4-bit envelope mode),
generating a futher 8 patterns.
These exactly matched the env volume 14, 12, 10, 8, 6, 4, 2, 0 sequences above.

I did the same for amplitude 2 , and each of these measurements matched prediction too.

(1) amp volume = 2, env volume = 1
0000000011111111000000000000000000000000000000000000000000000000 MASK
0000000010000000000000001000000000000000100000000000000010000000 ENV
0000000010000000000000000000000000000000000000000000000000000000 = prediction

(2) amp volume = 2, env volume = 2
0000000011111111000000000000000000000000000000000000000000000000 MASK
0100000001000000010000000100000001000000010000000100000001000000 ENV
0000000001000000000000000000000000000000000000000000000000000000 = prediction

(3) amp volume = 2, env volume = 3
0000000011111111000000000000000000000000000000000000000000000000 MASK
0100000011000000010000001100000001000000110000000100000011000000 ENV
0000000011000000000000000000000000000000000000000000000000000000 = prediction

(4) amp volume = 2, env volume = 4
0000000011111111000000000000000000000000000000000000000000000000 MASK
0000001100000011000000110000001100000011000000110000001100000011 ENV
0000000000000011000000000000000000000000000000000000000000000000 = prediction

(5) amp volume = 2, env volume = 5
0000000011111111000000000000000000000000000000000000000000000000 MASK
0000001110000011000000111000001100000011100000110000001110000011 ENV
0000000010000011000000000000000000000000000000000000000000000000 = prediction

(6) amp volume = 2, env volume = 6
0000000011111111000000000000000000000000000000000000000000000000 MASK
0100001101000011010000110100001101000011010000110100001101000011 ENV
0000000001000011000000000000000000000000000000000000000000000000 = prediction

(7) amp volume = 2, env volume = 7
0000000011111111000000000000000000000000000000000000000000000000 MASK
0100001111000011010000111100001101000011110000110100001111000011 ENV
0000000011000011000000000000000000000000000000000000000000000000 = prediction

==

I can't work out, however, to what extent there is (or is not) synchronisation
between the amplitude PDM sequence and the envelope PDM sequence. It seems
that the envelope PDM can change (when you clock the envelope generator
e.g. manually with ext clock) in the middle of a 64-cycle amplitude PDM sequence, but
the result remains aligned. I made a guess that the env PDM sequence (which
is really just 4 repetitions of a 16-cycle envelope PDM sequence) will switch
only at the end of one of those 16-cycle sequences -- which could well be
in the middle of a 64-cycle amplitude PDM sequence. But it does just seem
like we can switch between env PDM sequences at any point - certainly I have
evidence of examples where we switched at the 56'th point (so at the 8th cycle
of a 16-cycle env) and I *think* I also saw evidence of an example where
it switched at the 26'th point . So I think I'm assuming that there is left-to-right
synchronisation as we step through the 64 cycles in the PDM, but you can move
up and down through the tables at any time.

I tried to confirm if the same was true for moving up and down through the
AMP PDM tables - it also seems like these are NOT synchronised, so you can actually
switch amplitude at any time and you go up/down the table immediately (but at the same
point in the 64-cycle sequence)
That also makes sense given that this is effectively what happens as the oscillator
flips from 1 to 0 too (we effectively jump to the '0' amp PDM immediately - not at a 64-cycle
boundary)

===

NOTE THAT with env enabled, the lowest bit of the AMPLITUDE is ignored, i.e
amp volume 15 and 14 will sound the same when env is enabled (because the amp
PDM will use the sequence for amplitude 14 in both cases).

To test that, I set up an example using volume level 1, and enabling/disabling
env. with env enabled you should see no output, with env disabled you should see the
small pulses from vol 1. The experiment gave the expected result.

===

Remember that it's possible to use the env generators to generate output directly
with NO frequency enable bit? e.g. digital audio playback.
This works because the output value of the mixer when freq enable bit is set FALSE
seems to actually be 1 (not 0) if the ENV is enabled for that channel (so obviously
applies to only channels 2 and 5, as those are the only two channels that can have
envelope generator applied to them).
In other words: if freq mixer is disabled for channel 2, and ENV is enabled for channel 2,
then the outputs are clocked according to amp PDM & env PDM .
Unclear if this is a deliberate design or not. It feels like it probably is, since
this lets you use the hardware to fully synthesise triangle-wave and sawtooth-wave outputs
at audio frequencies, even though it doesn't seem to have been documented officially (but
certainly in very heavy use across Sam Coupe history).

In terms of its usage for digital audio playback: the technique is typically to
set the ENV controls to 'maximum volume' envelope, and (under software control) rapidly
change the AMPLITUDE according to uncompressed 4-bit PCM digital stream
(although actually only 3-bit resolution is provided by the SAA1099 due to the aforementioned
note that the LOWEST BIT of amplitude is ignored when ENV is enabled)

The noise generators do NOT seem to resynchronise when using the SYNC bit.
The sequencing does not seem to be simply reproducible by resetting the
registers and starting over.

When mixing only noise, the noise generator output is ANDed with the
(amp & env) PDM in the expected way.

When mixing tone and noise:
For 64 cycles: if freq osc is H then output amp & env
For next 64 cycles: if freq osc is H then output amp & env & noise
So on the 'HIGH' period of freq, even 64-cycle periods correspond to
H and odd 64-cycle correspond to NOISE (high or low); and on the
'LOW' period of freq, both 64-cycle periods correpond to LOW.
This effectively means the output amplitude of the noise component is
50% and the output amplitude of the freq component is 50% .

Note that there seems to be something odd - the mixed output will switch to
L on the odd periods 4 cycles too early (and also end 4 cycles too early)

So for example: if the AMP PDM is HLHHHHHHHHHHHHHH then on a regular
even/odd cycle (if noise is currently L) then you'd expect to see:

HLHHHHHHHHHHHHHH LLLLLLLLLLLLLLLL HLHHHHHHHHHHHHHH LLLLLLLLLLLLLLLL ....

but what you actually get looks a lot more like

HLHHHHHHHHHHHHHL LLLLLLLLLLLLLLLH HLHHHHHHHHHHHHHL LLLLLLLLLLLLLLLH ....
^ ^ ^ ^

(updated to add)- ah, my convention here is the opposite of the one used earlier in this thread, sorry.
I've represented a zero volume PDM as LLLLLLLLLLLLLLLL (or in the first table, a string of 64 0's) because my brain likes to think of zero volume as zero/low. In terms of output, the current sink is inactive, and the OUTL/OUTR is at 5v... So my H (or 1) represents 'moving away from 0 volume', i.e. current sink is active (and the OUTL/OUTR is now at a lower voltage) . Hope that's not too confusing.

My SAASound emulation library doesn't operate at the bitstream/8MHz level, it operates entirely at the 44.1kHz frequency, so it's really fast and runs in realtime on even basic PC hardware. The downside is that it does not emulate PDM exactly. It does emulate the behaviours discussed above, including the fractional envelope amplitudes, the weird effect when freq is disabled but env is enabled, the 3-or-4-bit env mode, and the 'lowest bit of amp is ignored when env is enabled' stuff, as well as the correct freq/noise mixing, but it does that through simple math, rather than through downsampling an 8MHz squarewave. That said, I'd like to improve the quality of SAASound , which is why I've also been reverse-engineering this chip at the bitstream level to see if it has any more secrets 😀