I figured out [...] in pygame.
Pygame is an actual modern, up to date environment and not anything on topic for RC.SE at all.
However, I have no clue what kinds of waves I need to make to produce a 'noise' sound effect like you see in older computer systems. I don't even know what kind of wave pattern I need to make for such a sound effect
There isn't a wave pattern at all, it's noise - the absence of a pattern. It's random frequencies in random mixture and random order.
Perfect, so called white noise (*1) is defined as a random signal with equal intensity at different frequencies. This resulting in a constant spectral density. Maybe you're old enough to remember a classic (pre digital) TV set turned to an unassigned channel? What's shown on the screen (and played on speakers) is white noise.
And like with any randomness there are many way to generate this.
In case of sound for computers, it just means generating a new random value per sound clock and output it. Today one may fill up a sound buffer with consecutive random values. A few hundret bytes to a few KiB will do it, depending on the quality you want. Then just loop that sound to your sound output (DAC) (*2)
Now, for early computers this isn't a great solution. In fact no data based solution would have been great, as it requires attention of the CPU and precious memory space as well. That's why they got sound generators. So the natural choice to add (white) noise was implementing a random number generator in hardware and send it's output to the DAC and amplifier chain (including optional filters). A simple way to do so is adding a Linear Feedback Shift Register with sufficient length to tap the desired data word and enough 'filler' digits to provide the right feedback. For quality of randomness length and feedback are connected - more feedback means less length needed.
As said, there are many other ways to generate randomness, the LFSR is just a common one, especially for early systems.
Example: The C64's SID
For example for Commodore's SID a 24 bit Fibonaci type LFSR was used (*3). More than enough for taping 8 output bits, but since they used only two feedback bits a shorter length would be way less desirable (*4).
Taping was done at bits 2, 4, 7, 11, 13, 16, 20, 22
Feedback was taken from bits 17 and 22, XORed together.
A pseudo code to generate SID noise may look like this
static unsigned int LSFR : 24 =0x7FFFF8;
unsigned int NewBit;
GetNoiseByte = LSFR<<7
+ LSFR[ 7]<<2
+ LSFR[ 4]<<1
+ LSFR[ 2]
NewBit = LSFR XOR LSFR;
LSFR = (LSFR << 1) OR NewBit;
This code will produce exactly the same number random sequence as a SID does (*5).
*1 - Other 'colours' of noise are pink, brown or grey, depending on the signal intensity (loudness) across frequencies. Strictly classic sound generators are never true white noise, but good enough for game purpose.
*2 - Nice side effect, when using (simple) software random number generators, such a sequence may have frequency properties noticeable to listeners when looped.
One way to keep that down will be to play a shifting window, that gets repositioned every repeat. Using prime numbers for buffer length, window and repositioning distance will reduce chances repetition a lot (and/or reduce needed buffer size).
The effect is even more noticeable when producing stereo, having different content in both buffers won't help (And using the same buffer twice produces only mono). Again a shifting window does solve this. Best by using a different offset value for each so no stereo artefacts repeat (early on). In fact, by doing so, a single buffer for both channels will be preferable, as it guarantees that the spectrum for both sides will be exactly the same.
A good combination of values may be
- Table Length: 1021 (biggest prime below 1024)
- Window Size: 1019 (next smaller prime)
- Offset First Chanel: 3
- Offset Second Channel: 5
With these values the pattern repeats after more than a million uses. At CD rate (44 kHz) that'll be after more than 25 seconds - wayout side any harmonics. For all practical use 251, 241, 3, 5 in would do as well - keeping all values in 8 bit range - ofc, now even more in need of good randomness.
*3 - Well, 23 were used, the lowest bit was always 1 to simplify the structure.
*4 - I guess there is more to it when going into all the math about, for real world estimations, remembering this simple relation will do it.
*5 - I do not recommend to use it. Just go ahead and use whatever random number generator sour modern system offers - they are way better and incredible more weighted in their randomness.