Sunday, 14 June 2015

INSIDIOUS 6581 Part 2: How on earth does someone copy a 30-year old sound chip?

View part 1 of this series here.

Let's copy the SID chip. How hard can it be?

To get a reasonable approximation of the SID chip is not that difficult. To get a useful interface to control it, though, certainly is. There's a fine balance between a capable interface and ease of use. It's easy to make a complex, convoluted interface that exposes every possible function, but difficult to make an intuitive one that still allows all the control that you need. What I wanted was an emulation accurate enough to be able to recreate some actual Commodore 64 game music, but one with an interface that didn't require you to be a programmer to understand it. The interface would evolve as the sound capabilities in the ensemble evolved.
A single channel in INSIDIOUS 6581
Compact, flexible, and hopefully easy to understand
for people who aren't me
By now, the SID has been pretty well researched and there is quite a lot of information about it. I searched the internet for detailed specifications on the chip so that I could get those details right in my emulation.

The fundamentals

The SID chip is actually quite simple. It doesn't really do very much (although compared to most other sound chips, it's vastly more capable). A few waveforms, some combination modes and a filter. And that's about it.
Triangle, Sawtooth, Square and Noise waveforms
There are 4 fundamental waveforms: Triangle, Sawtooth, Square/Pulse and Noise. The reasons that these waveforms are present and not others like Sine is because they are easy to generate using counters and timers, which themselves are easy to create in hardware. A sawtooth wave, for example, can be created by regularly increasing a value by the same amount over time and then resetting it to zero when it reaches its limit. A square wave can be created by regularly flipping a value between zero and one. It's very simple stuff.

The SID can also combine these waveforms together to give some extra sounds. How it does this is even now still under debate as what the original hardware specification states does not match the sound that is produced.

A SID musician
Ring mod/Hard sync
are hard to demonstrate
in one image, so here's
an alpaca instead
The two most advanced features are ring modulation and hard sync. Both require two channels to operate:

Ring modulation is an effect where the amplitude of one waveform is modified very quickly by the shape of another. Basically, imagine if you could play waveform A with a volume knob that could go below zero and turn the sound upside down. You then turn its volume knob incredibly fast in a pattern that matches the shape of waveform B. Ring modulation is responsible for bell-type sounds and most of the weird screechy noises that litter Rob Hubbard tracks. It only works on the SID when waveform A is set to be a triangle waveform.

Hard sync is where one waveform plays at its own frequency, but gets reset back to its start every time the second waveform loops. It is responsible for the rising modulating sound in Ben Daglish's Wilderness music from The Last Ninja and the ludicrous intro noise in Martin Galway's Roland's Rat Race music.

Lastly, the SID has a filter that can be configured as Low-pass, Band-pass, High-pass or combinations of all of them. There is only one filter, but each sound channel can be selectively routed through it or not. It is the filter that can make the SID really stand out. A bug in the implementation means that it distorts quite easily giving a nice warm, growly tone. It was not very widely used in games because instability in the design and manufacturing process meant that the intensity of the filter could be massively different between two chips. Martin Galway, when asked about his best and worst memories of the C64 remarked, "Worst memory is that damn filter! I wish they were able to fix it."

The hardware has some other features, like the ability to route external audio through its filter, two analogue-to-digital converters (intended for reading paddle controllers) and there is are also a bug that allows sample playback, but they are not relevant for creating an emulation of its synthesis capabilities.

Starting the recreation

Reaktor's Tri Sync oscillator moduleRight at the very beginning, it became clear that choosing to use Reaktor was a good idea. Reaktor, being a modular synth, has a whole load of different diverse modules to wire together, a group of which are various oscillator types. Clearly seeing into the future and noticing that I wanted to copy the SID chip, Native Instrument provide oscillator modules for Sawtooth, Triangle and Pulse (Square) that have a facility for both ring modulation and hard sync. What a stroke of luck. I was expecting to have to construct the oscillators from scratch using Reaktor's 'Core Cell' facility, whereby you can perform very low-level operations on raw audio data, but it was already done for me.

The top-level of a single SID channel in INSIDIOUS 6581
Wiring up the three oscillators and adding an LFO to vary the pulse width on the pulse oscillator was very easy and instantly produced that whiny C64 lead PWM sound. It annoyingly varied the DC offset with the pulse width value, but that was easily solved by subtracting the pulse width value from the whole waveform to balance it back out again.

Who noise how to solve it?

Adding the noise channel was rather more of a problem. The noise oscillator in Reaktor is described in the manual as producing "a random signal containing equal amounts of all possible frequency components". That means that it is what is known as 'white noise'. To get theoretically perfect white noise you take an infinite number of sine waves at every possible frequency and add them all together at the same volume level. The problem is that the SID noise waveform is by no means white noise. It can be pitched up and down as can be clearly heard in the title music of Uridium by Steve Turner. Because white noise represents infinite frequencies, pitching it up or down makes no sense, so how to re-create the SID noise if not with the Reaktor noise oscillator?

White noise, probably
The noise waveform of the SID is generated by a pseudo-random number generator. It streams out random numbers at whatever frequency it is asked to. When the frequency is lower, the numbers change more slowly and when the frequency is higher, the numbers change faster. There are actually other noise and random generators in Reaktor, but they all have the same problem. I toyed with the idea of creating my own noise module to copy how the SID generates random numbers, but that would have been unnecessary. Whatever would have come out of the output would actually end up being no different than a recorded sample of white noise, which is a fixed set of random numbers. By pitching such a sample up and down, the output numbers change faster or slower, exactly like the SID. So that's what I did. I generated 3.5 seconds of white noise (long enough to not notice any repeating loop) and inserted it into a Reaktor sample player for use as the white noise channel. I then set the sample player to playback at 'poor' quality (so as not to have any smoothing) and tuned the root playback frequency so that the tone matched the other oscillators.

The combined waveforms

The Triangle + Square
waveform, which sounds as
scratchy as it looks
When you instruct a SID to use multiple waveforms at the same time on one channel, the hardware specification suggests that it performs a binary AND operation on the waves. It doesn't. It does something very weird. I tried to find out exactly what happens so that I could make a Core Cell to copy it exactly, but it's very difficult to get the right information. I didn't actually need to go that far because the output of the combined waveforms is deterministic; it always comes out the same for the same combination of waveforms.

The mostly useless
Triangle + Saw waveform
Whether I generated the waveforms in real-time or just played back a tiny sample from a real SID chip, the result would be exactly the same. It felt like cheating, but using samples is a whole lot easier, so that's what I did. Unfortunately, this is the worst-sounding part of the emulation. When the samples are pitched up, they alias quite badly. I tried to resolve this by having a separate sample for each octave, but the Sample player in Reaktor wouldn't work properly once I'd set it up. I do intend to resolve it somehow, but for now there is modulating aliasing on the combined waveforms.

I wanted to support every possible feature, so I needed to support hard sync on the combined waveforms. As they are samples and don't have a built-in Sync features like the fundamental oscillators, I had to hack it in. The sample player supports resetting the playing sample back to the start when it receives a positive signal at one of its inputs. I set up a mechanism to generate events with a value of one to restart the sample waveform every time the input sync waveform looped. It works, but Reaktor is not entirely happy about it and clicks and pops a bit. It's the best I could do and seeing as all but one of the combined waves are barely audible anyway, it's of very limited usage so its poor quality is not a deal-breaker for me. I don't think any C64 game music uses hard sync on a combined waveform.

The end?

Believe it or not, that is the complete implementation of all of the (documented) sound generation features of the SID chip. I could have stopped there, but all I would have been left with would have been a crappy-sounding monophonic synth. It needed the filter and some extra control features to make it sound how it should.

Next: "When a filter doesn't behave"

No comments:

Post a Comment