Thanks to NewRisingSun's DosBox patch to log CMS/GameBlaster audio into VGM files, there is now a way to get some source material for experimenting.
So I wrote a simple program that can read the DosBox-dumped VGM file and understand barely enough of it to render some GameBlaster audio output to a file.
At first try it was just outputting garbage, but after fixing one stupid bug, try to guess the look on my face when I first heard this!
The flac file is post-processed in Audacity, it is resampled from 3.58MHz to 44.1kHz and then high-pass filtered to remove DC offset.
So the renderer does not (yet) output audio file that could be played normally.
Let me know what you think.
I was kind of hoping it would sound somewhat better though, but what can you expect from square waves, no matter how great they are resampled/processed.
Also, this made me realize that VGM file can't properly handle external triggering of envelope generators, but fortunately this music does not use envelope generators at all.
@Jepael: Here's the tool I wrote to play around with the SAA1099's registers (channels 0 through 2 only). You can use mouse and keyboard (space bar sets the values). DOSBox 0.74 has a bug with the mouse position in the 40x25 text mode so I would recommend using a different build. Hope this helps out with the emulation work. 😀
I haven't found a believable documentation for it, so took a whack at the SAA1099 noise generator and decided to document it.
It is possible that the exact noise generator algorithm was already known, but it may be written in different form (Galois vs Fibonacci, left shift vs right shift, etc).
I controlled the chip to generate noise at some sane speed (about 3500 Hz update rate) and recorded it on another PC.
Visual inspection revealed that the noise has a period of approximately 262000 bits. That's about 2^18 bits, not a coincidence. Also, the longest low period encountered was 17 bits, and longest high period encountered was 18 bits.
This revealed it most likely is a 18-bit maximal length LFSR with period of 262143 bits. Not all sound chips use a maximal length LFSR, so never assume it is. Given the longest periods seen, the 18-bit period must be ones out from the LFSR, and the 17-bit period must be zeroes, as an LFSR can't have all bits zero or it's stuck with zeroes.
And since there are many tap configurations that can do it, most likely it's one with least amount of feedback taps, because why waste precious silicon area. Turns out the minimum tap count is two and there are only two different tap configurations (well, just one and it's reverse). After finding a good starting point in the stream, the next bits easily reveal the feedback taps, so it was easy to determine which one it is with pen and paper.
1int saa1099_prng(void) 2{ 3 static uint32_t lfsr=1; // seed unknown, must be non-zero 4 5 /* 6 SAA1099P noise generator as documented by Jepael 7 18-bit Galois LFSR 8 Feedback polynomial = x^18 + x^11 + x^1 9 Period = 2^18-1 = 262143 bits 10 Verified to match recorded noise from my SAA1099P 11 */ 12 13 if (lfsr&1) 14 { 15 lfsr=(lfsr>>1)^0x20400; 16 return 1; 17 } 18 else 19 { 20 lfsr=(lfsr>>1); 21 return 0; 22 } 23}
Then a comparison of recorded noise versus noise generated with above algorithm:
The attachment SAA1099_noise.png is no longer available
Looks like a match, and it continues to match throughout the waveform, until the 262143-bit noise period ends. I can't believe I actually got it to match at first try.
What's next? I don't know, but I ran into some undocumented feature that enables higher noise frequencies than the ones documented.. Oh and current emulators tend to understand "mixing" noise and tone channels rather differently how the chip actually does it.
Pretty interesting findings! If you have a chance, would you mind elaborating how the undocumented noise at higher frequencies is triggered? I'm only familiar with setting the noise generator control to 0x03 (for generator 0) at address 0x16 to drive the frequencies higher.
Pretty interesting findings! If you have a chance, would you mind elaborating how the undocumented noise at higher frequencies is triggered? I'm only familiar with setting the noise generator control to 0x03 (for generator 0) at address 0x16 to drive the frequencies higher.
Thanks, sure thing.
Hmm, I think you can only get lower frequencies than the three presets when the noise is clocked by tone generator.
Fixed rates for clocking the noisegen are X=27965 Hz, X/2, and X/4, while with a tone generator clocking it you can get up to about X/2 maximum as well.
The trick is to set the tone generator reset bit while the tone generator is used to clock the noise.
Apparently that makes the noisegen to be clocked by the undivided octave clock, so lowest octave 0 is same as rate X. And each octave higher doubles the noise frequency. You can't hear much at the highest octaves though, as they are beyond human hearing range 😀
But the downside is, you can't generate any tones when the tonegens are in reset, so it's not a very useful trick.
Jepael wrote:Thanks, sure thing. […] Show full quote
Thanks, sure thing.
Hmm, I think you can only get lower frequencies than the three presets when the noise is clocked by tone generator.
Fixed rates for clocking the noisegen are X=27965 Hz, X/2, and X/4, while with a tone generator clocking it you can get up to about X/2 maximum as well.
The trick is to set the tone generator reset bit while the tone generator is used to clock the noise.
Apparently that makes the noisegen to be clocked by the undivided octave clock, so lowest octave 0 is same as rate X. And each octave higher doubles the noise frequency. You can't hear much at the highest octaves though, as they are beyond human hearing range 😀
But the downside is, you can't generate any tones when the tonegens are in reset, so it's not a very useful trick.
That's really interesting to know. I'll have to try it out to hear what it sounds like. The more I understand about this chip, the more I come to appreciate what it can actually do. 😀
Jepael: I updated the saa1099 core in MAME to use the correct lfsr polynomial: https://github.com/mamedev/mame/commit/c11c22 … e3be48ba31786f8
I also added a note about the fact that if the noise channels are set to be clocked by a tone channel, and the 0x1c bit 1 reset bit is set, they get clocked by the undivided octave clocks, but I did not implement this yet.
Are there any other obvious issues I should try to fix from elsewhere in this thread?
LN
"When life gives you zombies... *CHA-CHIK* ...you make zombie-ade!"
Lord Nightmare wrote:Jepael: I updated the saa1099 core in MAME to use the correct lfsr polynomial: https://github.com/mamedev/mame/commit/c11c22 … e […] Show full quote
Jepael: I updated the saa1099 core in MAME to use the correct lfsr polynomial: https://github.com/mamedev/mame/commit/c11c22 … e3be48ba31786f8
I also added a note about the fact that if the noise channels are set to be clocked by a tone channel, and the 0x1c bit 1 reset bit is set, they get clocked by the undivided octave clocks, but I did not implement this yet.
Are there any other obvious issues I should try to fix from elsewhere in this thread?
LN
The tone+noise mixed together on same channel is not very easy to explain. PWM of amplitude modulator is emulated as pure amplitude level.
Tone off, noise off : output value idles high. Easy, it's zero.
Tone on, noise off : output value toggles at tone frequency, between idle high, and active low. Low period is modulated by 16-step PWM amplitude (max vol 15 means it's 15 PWM times low and 1 PWM time high. min vol 0 means silence, output value idles high). Easy, it's tone at max volume.
Tone off, noise on: output value comes from noise bit, between idle high, and active low. Low period is modulated by 16-step PWM amplitude. Easy, it's noise at max volume.
As described in my previous post, I am positive than noise generator output bit 1 is the idle high, and bit 0 is the active low.
Tone on, noise on:
-when tone value is at one state (high?), output idles high. The noise has no effect here. (So it is not a sum of tone+noise).
-when tone value is at the other state (low?), and noise is low, the signal is low at full amplitude set by PWM.
-when tone value is at the other state (low?), and noise is high, the signal is high every other PWM period, and low every other period at amplitude set by PWM. So this averages the signal to be low at only half the amplitude.
I've tried to implement all but that latest post(about tone/noise off/on states, which will be using PWM). I remember it was originally based on Dosbox, but looking at Lord Nightmare's commit and saa1099.cpp code, it seems that either Dosbox uses the same codebase or I've based mine off mame's. Can't remember though(many years ago, before moving to bitbucket and starting to use git).
I have this strange problem: when I try to play the Monkey Island music(besides being slow due to 14MHz/2 sampling rate being downsampled and rendered at ~44.1kHz by ticking at the hardware base 14MHz rate that's used), I get way too high tones for some reason? Anyone can see what's going wrong?
It uses a base 7MHz(14MHz divided by 2) to render it's samples(using PCM), then high-pass filters it by 18.2Hz, then low-pass filters it at the nyquist rate before sending the samples at ~44.1kHz to the renderer's buffer.
Anyone can see what's going wrong? Why are the tones higher than it's supposed to be?
Anyone can see what's going wrong? Why are the tones higher than it's supposed to be?
12*GAMEBLASTER.baseclock/512
That's exactly two times too big, result should be 13982.xxx Hz. Try if it fixes it.
Sorry I don't have a personal bitbucket account to post a pull request.
I've tried to implement all but that latest post(about tone/noise off/on states, which will be using PWM). I remember it was originally based on Dosbox, but looking at Lord Nightmare's commit and saa1099.cpp code, it seems that either Dosbox uses the same codebase or I've based mine off mame's. Can't remember though(many years ago, before moving to bitbucket and starting to use git).
Yes, DOSBox's code for SAA1099 was based off MAME's.
Don't worry, that code was relicensed in MAME as being licensed under the 3-clause BSD license as of March 30, 2016. Prior to that, DOSBox wasn't following our license. 😜
"I see a little silhouette-o of a man, Scaramouche, Scaramouche, will you
do the Fandango!" - Queen
I've improved the Game Blaster sampling(removing the multiplication of 2 on the frequencies(both square wave output and RNG) and resampling to support seperate, independant Game Blaster and Output rendering(the same way as the Sound Blaster rendering): https://bitbucket.org/superfury/unipcemu/src/ … ter.c?at=master
One strange thing: with a divider of 2 the output sound fine(full quality rendering), but when I enable the commented out 512 divider(to get a low samplerate for testing), I get ticking without sound outputted(it should be rendering at ~29kHz on the Game Blaster and recording(rendering to output, using the MHZ14_RENDERTICK define) at ~44.1kHz). Anyone can see what's going wrong there? I simply wanted the two decoupled to do testing on it(Rendering at ~7MHz is pretty heavy, even on a 4.0GHz i7 CPU, slowing down emulation greatly).
Edit: Managed to fix the bug: it was loading the frequency modifier with the base rate(which never changes, always being ~7MHz) instead of the required sample rate, thus producing samples at a 7MHz rate instead of a ~29kHz rate when the divider is set to 14MHz/512.
The only problem still left is that rendering the Game Blaster is still pretty heavy, even on even a good CPU(4.0GHz i7), causing it to slow down and having a heavy latency problem. Is there any way to improve that? This currently amounts to ~7M fmodf executions each second, taking up almost all execution time on the single CPU it's ran on.
Yes, DOSBox's code for SAA1099 was based off MAME's.
Don't worry, that code was relicensed in MAME as being licensed under the 3-clause BSD license as of March 30, 2016. Prior to that, DOSBox wasn't following our license. 😜
One thing I'll add is, only the SAA1099 code present in MAME from March 30, 2016 and newer is relicensed 3-clause BSD, so I guess to be technical, to use the code you must pull from March 30, 2016 revision or newer, then apply your changes, to use the relicensed code. Or verify that there's no changes between your code and the code in MAME.
"I see a little silhouette-o of a man, Scaramouche, Scaramouche, will you
do the Fandango!" - Queen
I simply used a semi-clean room design: since I'm quite forgetful(forgetting most things within a minute), I read the original code, remember the things that happen(generic formulas etc.), then write my own code based on the formulas remembered(can be clearly seen in my implementation of the volume envelope(the repeating numbers) in UniPCemu. The only cause for even reading the code is because I can't find any documentation on the hardware(strange though, since it's such a generic Phillips chip).
Optimized signal generation(square waves precalculations) mostly. Also disabled the high-pass filter of 18.2Hz(It removes lots of sounds generated during the The Secret of Monkey Island opening, because of an unknown reason). The full 7MHz rate is still too heavy to use effeciently, unfortunately.
This is my latest revision of UniPCemu's (originally loosely based on Dosbox/MAME's Game Blaster core) sound generation. I've optimized it a lot to be able to run decently at high frequencies(~3.57MHz(14318180Hz divided by 4) samplerate).
I've implemented the post of Jepael of 26 Jan 2017, 22:55 about "Tone off/on, Noise off/on" combinations. I hope I understood it correctly?
Also the filters have been disabled(they're too heavy on CPU load). I've recorded some playback(resampled to 44.1kHz with lowest-neighbor resampling, no filtering(except for 18.2Hz low-pass after resampling to 44.1kHz)).
Edit: For some reason, the recordings sound a little bit off. Anyone can see what's the cause for it? I know that the sample rate is divided by the frequency(floating point) that's set and rounded down(uint_32 storage) to create the overflow number(after how many samples to flip the PCM output from 0 to 1 or vice versa, using a simple XOR 1 operation), but this shouldn't be much of a problem, as it uses about 3.5MHz samplerate(over 350 times the nyquist frequency of 22050Hz to represent the signal), so it should only be off a very tiny bit? Anyone can see what's going wrong? This happens to both the Dosbox/MAME formula-generated output and the current one(based on Jepael's post).
This is what I get when I generate a 500Hz square wave using the current algorithm(pure output, unmodified):
The attachment testgameblaster500hz.zip is no longer available
Using this code:
1 //Load test wave information for generating samples! 2 GAMEBLASTER.chips[0].squarewave[7].timeout = (uint_32)(__GAMEBLASTER_BASERATE/(double)(500.0f*2.0f)); //New timeout! 3 GAMEBLASTER.chips[0].squarewave[7].timepoint = 0; //Reset! 4 GAMEBLASTER.chips[0].squarewave[7].freq = 500.0f; //We're updated! 5 6 WAVEFILE *testoutput=NULL; 7 8 uint_32 i; 9 byte signal; 10 11 testoutput = createWAV("captures/testgameblaster500hz.wav",1,(uint_32)__GAMEBLASTER_BASERATE); //Start the log! 12 13 for (i=0;i<__GAMEBLASTER_BASERATE;++i) //Generate one second of data! 14 { 15 signal = getSAA1099SquareWave(&GAMEBLASTER.chips[0],7); 16 writeWAVMonoSample(testoutput,signed2unsigned16(signal?(sword)32767:(sword)-32768)); //Write a sample! 17 } 18 19 closeWAV(&testoutput); //Close the wave file!
Just before initializing all channel frequencies(right after the initSoundFilter calls).
Is the generated signal correct? According to my hex editor(WavePad refuses to open it, while Windows 10's Groove Music opens and plays it without problems), it switches state every 3579 samples, with a full wave taking 7158 samples(At a 14MHz/4 rate, thus a 3579545Hz samplerate), thus amounting to a 500,xxx...Hz signal?
Edit: So the input signal seems correct? Then why is the output signal in the Game Blaster emulation so strange(off-tune)?
Edit: I've recorded the Monkey Island opening from the Secret of Monkey Island and the first song(using UniPCemu's normal 44.1kHz recording, which is only high-pass filtered after resampling to 44.1kHz).
The attachment MonkeyIsland_3.5MHzResampledTo44.1kHz_UniPCemu_20170131_1641.zip is no longer available
Anyone can see why the sound isn't as sharp as Dosbox's and MAME's? Is this simply because the sample period(in samples for each wave half) is rounded down, instead of floating point? (Compared to https://www.youtube.com/watch?v=SEyIcNDDAB8 )
I've lowered the Game Blaster 3.5MHz signal by a factor of 16(since the samples already have 16-level PCM applied, Dosbox-style), making low-pass 22050Hz filtering possible again with reasonable speed.
Sound Recording of UniPCemu commit 2017/02/01 17:48 playing the sound at 3.5MHz further divided by 16(as the samples already have 16-level PCM applied when rendering the Game Blaster), low-pass filtered at 22050Hz(nyquist frequency of 44.1kHz), high-pass filtered at the emulator's 18.2Hz(minimum PC-speaker frequency):
The attachment MonkeyIsland_3.5MHzDIV16_lowpass22kHz_ResampleTo44.1kHz_UniPCemu_20170201_1748.zip is no longer available
Although emulation should be more accurate now, for some reason the sound is still a bit strange, compared to the recording at the start of this thread. Anyone can see why my emulation is off? What's going wrong?