Blog

  • How the Oscillators Work

    Next Tuesday I’m spending an afternoon with Chris Huggett’s OSCar at PWM HQ, to find out how far I am from this prototype sounding and behaving like the original, so I can close that gap. I expect it to go about as well as any first technical rehearsal: the stated objective is to learn, but the real purpose is to instil fear.

    A few features are still missing. As long as the LFOs, envelopes, filters, pitch, and VCAs are somewhat wired in, refinements can wait. Without my own fully-kosher OSCar to play, it’s surprisingly tiresome to work through two entire signal chains and prove something even as trivial as the fact that I’m sounding at the correct octave. If there’s any compensation to working blind, it’s obtaining a depth of understanding that probably surpasses Chris Huggett’s of the time. It’s anybody’s guess why I’d want or need that, though …

    Anyway, Superbooth is looming. I never get to go myself, because I’m too busy sending products and features. I’ve promised some kind of playable demo, but it’s taken a lot longer than expected to make the modulation work. Such things always take time, incurring familiar long cycles of break-it-then-fix-it. But this work is also a grind: low-dopamine; high-effort; lots of carefully checking number ranges across two implementations. It breaks in funny ways in unexpected places. The code is a bit smelly, and reorganising it into something maintainable takes a lot of thought. But it’s essential, or why do this at all? Occasionally I lose half a day just moving blocks of it around, and confirming that it’s correct. All of which is making it hard to sleep, and one cannot do this kind of work during insomnia.

    Knowing what to change and when is a constant problem, because old code involves a quantity of enquiry, and I find myself adding the comment ;BUG rather often: if it is broke, there has to be a reason not to fix it.

    Sorry, where was I? Alex Ball makes some great videos featuring an original OSCar. And the comment thread below reveals that a couple of people at least do want to know how OSCar generates its sound. What follows is therefore a more-than-usually technical post about some of that.

    Did I mention oscillators?

    The short answer: OSCar’s got two wavetable oscillators. Each one scans its own table of 256 samples per period.

    The long answer is that this was far harder to do in 1983 than you might expect. We end up with a circuit and implementation which is, to pull the apposite quote from @RegebroRepairs above:

    SUPER weird.

    Just one DAC

    Remarkably, every analogue voltage that the OSCar generates — two oscillators and 7 control voltages — come out of the same mono 8-bit DAC. It gets switched a lot.

    This is a slide from my still-unpublished ADC talk (and the AES talk before that: too expensive to use only once) which demonstrates what the DAC is outputting over time. I was measuring my own OSCar clone, which runs at half-speed so I can emulate the ROM and RAM using a cheap ARM chip. (That made debugging a lot easier and saved me needing to buy an EPROM programmer — more bloody stuff — but I couldn’t quite make it run reliably at 4MHz.)

    The upper graph shows two sawtooth waves, of different frequencies, that the DAC is chopping between constantly: oscillators 1 and 2. The parts I’ve coloured red are where the interrupt service routine chucks out nine control voltages that OSCar also isolates and holds in separate op-amps:

    All the control voltages. I did redraw this in CAD, but I’m a fan of Chris’s penmanship.

    The control voltages are updated via the DAC at the beginning and end of the interrupt. I think this doubling was to let Chris measure how long OSCar spent in that routine. The trace above shows it taking about 3.6 milliseconds (call it 1.8ms at full speed), in which it updates the LFO, glide, gating, PWM, filter and pitch control voltages, and checks the keybed, switches, and voice allocator. Considering that a single multiply takes about 80 microseconds, you can imagine the terseness of this code. The 244Hz control rate is a little low by modern standards: Mantis operates around 600Hz.

    Why nine control voltages? OSCar uses 7 for internal amplifiers and filters; there’s an eighth (‘ANALOGUE REF’) that doesn’t connect to anything in practice. It was clearly intended for calibrating the VCA/VCF chips, but Chris found a more elegant solution to make them self-regulate. The ninth value is a nonsense one that’s pushed out to trigger fluctuations in logic levels so that the other eight go through correctly. One of many kludges.

    Going synchronous

    In most new digital synths, and certainly everything that Chris designed between OSCar and Peak, the output DAC is driven at a constant rate: often, in Chris’s work, a weird choice derived from the system clock. The relationship between this rate and the pitch of the notes being played is completely arbitrary, so any wave tables need to be interpolated. A lot of care needs to be taken to do this well: particularly in the transition where the phase wraps around. Naive oscillators suffer from aliasing, which sounds unpleasant.

    (If you’ve listened to a few old computer game soundtracks, the older uploads would alias horrendously. People who wrote sound trackers and chip-synth emulators in the early days have now either brought in experts to handle this stuff, or have spent some time learning the theory. I was casting for some examples on YouTube, and they now all sound fine.)

    Fast computation is essential: most of Chris’s synths, from the Supernova to the Mininova via the A- V- and X-Stations he did for Novation, used the Motorola 56000 series of DSPs. The kind of power those chips enabled is cheap and abundant today. Pretty much every modest microcontroller comes with a free synchronous audio interface — most use a standard called I²S — designed precisely for a wide, fast, synchronous DAC. The Z80 is nowhere near fast enough to do the computation for this approach: as I wrote in last week’s post, it’s about 5% of the way to making a digital volume control.

    So we’re stuck with 8-bit arithmetic and an 8-bit DAC. The best-case performance of an 8-bit DAC is one part in 256: around 50dB of signal-to-noise ratio. The way to make that sound good is to vary its sample rate continually. In OSCar, it’s always a power-of-2 multiple of the pitch we’re playing.

    These images are from an earlier time in my research. Some of the waveforms are drawn backwards. Same frequency content, either way.

    We don’t need to do any interpolation if we work like this: we’re essentially doing it in the time domain. Naive-looking waveforms like the ones above — OSCar’s actual tables — work just fine.

    Making the audio frequency and the sampling frequency harmonically related is a special case of aliasing: all the quantising distortion occurs at harmonics of the note frequency, so it sounds intentional. That’s how OSCar gets away with it.

    Counters, counters everywhere

    When we scan a 256-word wave table to reproduce, say, middle C, the sampling frequency will be 256 times 261.63Hz: about 67.0kHz. Two conclusions follow from this simple calculation:

    • That’s higher than the Compact Disc sampling frequency. OSCar can produce this kind of rate, just. We hit a performance ceiling somewhere around 70kHz where the data cannot be output quickly enough, and we compromise by skipping alternate samples in the wave table.
    • To generate that sort of frequency directly from a 4MHz clock means dividing by about 59.7. We can only divide by integers, so if we accepted the worst-case performance for a divider of that level, it’d be about 1 part in 200, which is 8 cents. An octave higher, it would be 16 cents.

    A couple of cents of pitch error (1/600th of an octave) is about the acceptable accuracy for a modern synth, which requires a clock accuracy of about one part in a thousand (2^(1/600) = 0.11%). To achieve that, we could start with a clock about a thousand times the speed of the highest frequency we’re trying to generate. If we’re maxing out around 70kHz, we’d need to start around 70MHz.

    Circuit boards and chips of this era, built in the way OSCar is, aren’t going to handle 70MHz waves without upsetting the whole street’s FM radio reception. Rather than divide from a fast clock, Chris generated a slower clock at 8 times the audio frequency (261.63 x 8 = 2093Hz), and then added circuitry to multiply that frequency by 64x to bring it up to 134.0kHz. A couple of counters then divide that: one divides by two to make the 67.0kHz clock that drives the audio circuitry; another continually counts down from 256 to produce the wave table cursor.

    Simplified clock tree for OSCar oscillator 1.

    All this ensures that the division factor is somewhat more than 1000 (4MHz / 2093Hz = 1911) which exceeds our pitch accuracy target. To manage all this, OSCar needs a few more components: a phase-locked loop surrounded by dividers to multiply the 2093Hz signal up, then divide it back down again to close the control loop, and a ton of counters to perform administration.

    Suddenly the timer architecture gets complicated. There are seven separate counter/dividers in OSCar, spread across two chips:

    1. Intel 8253 counter 0. Sets the 244Hz time base for the Z80’s interrupt so all the control-rate logic happens regularly.
    2. Intel 8253 counters 1 and 2. These divide the 4MHz system clock into a simple multiple of the note frequency. Oscillator 1 and 2 respectively.
    3. Z80 CTC counters 0 and 2. For oscillators 1 and 2 respectively, these divide the 512x note frequency into the sample rate used by the system: usually it’s a divide-by-2 but higher pitches divide by as much as 16.
    4. Z80 CTC counters 1 and 3. For oscillators 1 and 2 respectively, these count down from (usually) 256, clocked at 256x the note frequency. These hold the indexes of the samples we’re currently reading out from memory.

    All kinds of refinement are built into this, including two separate mechanisms for skipping samples in the wave tables as the frequency increases.

    The clock multiplier also contains a complication: a mixed-signal hack that had me staring for days at the schematic in bewilderment, trying to understand why sections of the 4MHz clock were being chopped into the PLL control signal. Long story short: it improves the phase synchronisation between the processor and the multiplied-up signals.

    We’re trying to replicate the sound and feel, not the component-level technology. But my duty here doesn’t stop at being a keyboard technician, or even just a synthesiser designer. Rather, there’s an unusual responsibility to create something new that sounds old. So it’s important to understand this stuff in a way that few people ever will.

    The system clock on our new OSCar is 72MHz instead of 4MHz, which just about allows us to do away with the multiplier without losing the required precision. But I kept the external phase-locked loop in the new design for character: it’s nice to have a free-running VCO controlling the sample clock. My compromise was to change it from 64x to 8x.

    All those counters are built into the microcontroller, which simplifies the rest of the circuit. And a lot of the firmware kludges to deal with high notes can be simplified because we now have the ability to decide where to put our play-head before we output each sample.

    For very low pitches, the sample rate begins to enter the audio spectrum. This is a problem because you can hear that pitch clearly if you have reasonable high-frequency hearing. OSCar actually won’t play beneath the F two and a half octaves below middle C, which has a 11.8kHz sample rate. The new OSCar goes right down to MIDI note 0, more than two octaves beneath this, and we achieve that quietly by choosing a higher rate and repeating samples, which the old OSCar couldn’t do.

    The RFSH hijack

    The Z80A takes a microsecond, best case, to execute an instruction. If it were outputting 130,000 samples a second under its own control, the processor wouldn’t be able to do much else.

    Chris circumvented this limit by making surreptitious use of a feature that the original Z80 was (to the best of my knowledge) the only microprocessor ever to include: refresh logic.

    There’s two types of RAM, broadly speaking: static RAM, which is the bistable architecture with crossed gates that you’d remember if you took GCSE electronics:

    with thanks to eeguide.com.

    Then there’s dynamic RAM, which is a single capacitor that stores whatever charge it’s told to (high or low), until it’s needed again.

    ibid.

    Normally you’d use dynamic RAM in a project. It’s simpler and therefore cheaper. It uses less power when it’s working. But that convenience isn’t free. That capacitor needs to be read and rewritten fairly frequently or it’ll discharge and forget its data. During the time it’s being refreshed, of course, the processor cannot access it.

    Fortunately, there’s a bit of time during each instruction fetch where the Z80 is not using the memory bus. As the timing diagram below shows, two clock cycles are spent fetching an instruction from memory; two more are spent decoding and executing it.

    During the two cycles where the bus is idle, the Z80 puts its RFSH pin low, which signals to any connected logic that it has 500 nanoseconds to do some capacitor refreshing without having to halt the processor. It also chucks a number onto the address bus, which is a counter that increments on every instruction fetch and wraps at 127. The idea is that the designer might use this counter to simplify their refresh circuitry.

    Instruction fetch. From p9 of the Zilog Z80A data sheet.

    This cadence isn’t guaranteed, by the way: the hungriest regular instructions take around 20 clock cycles to execute. That’s five microseconds between RFSH events. With the need to keep two oscillators and the control voltages up-to-date, it explains the 70kHz sample rate limit.

    OSCar uses static RAM. There are two reasons for this. The first is that, as long as we supply it with a voltage, it takes only a tiny amount of current to remember its contents indefinitely. With the aid of a rechargeable nickel-cadmium battery, the static RAM in OSCar holds its presets, waveforms, and sequences permanently even with the power off. The second reason to use static RAM is that we can hijack the RFSH signal and use it for another purpose.

    In a crowded field, this is probably the most ingenious feature of OSCar, because it makes the two oscillators possible. The Z80 CTC chip, containing four of the counters we use, has its data pins wired to the address bus, not the data bus. We communicate with it by writing to specific addresses. Any data we put on the bus is ignored, and the Z80 cannot read back from it.

    The reason for this peculiar wiring is that, during RFSH, a few logic chips wake up, one of which isolates eight bits of the address bus from the Z80 processor (which otherwise is the only device allowed to write to it). They also request the current value from the wavetable counter.

    The count value finds its way, because of the novel wiring, onto bits A8 to A1 of the address bus. We let the Z80 control A0 as it toggles every time there’s a refresh cycle: the two wave tables are interpolated in memory. A15 to A9 are also set by the Z80, by some of the very first instructions the processor executes when it’s turned on:

    0002 : LD A,6
    0004 : LD I,A

    This ensures that the upper byte of the address that appears during refresh is 0x06, which matches the place where we keep the wave tables, at addresses 0x600 to 0x7FF on that chip. Because the counters count towards zero, the waves are stored backwards in time. Which gets a lot more confusing than you think it would.

    With the correct sample address on the address bus, more logic forces the RAM chip to output its data. The RAM puts this on the data bus a few nanoseconds later, whereupon the DAC is instructed to convert it.

    A short interval later still, a delayed clock pulse goes high to load the converted voltage into the output amplifier.

    TL;DR: There’s absolutely no code for reading out the wave tables. This was the high point of an era when a lot of firmware was essentially embodied in hardware. A small number of logic chips run the wave table readout in half-microsecond snatches while the Z80 is busy running its program.

    When I’m asked, as I am occasionally, why we couldn’t build our new OSCar using one of the modern in-production variants of the Z80, and simply modernise the old code and fix the bugs, this is the most consequential of many answers. As Zilog refined the Z80, its architecture changed. Instruction fetches are now pipelined, which means that they happen while the previous instruction is being executed: four clock cycles become two. The newer chips also have 16-bit logic units, so most instructions will run in only one clock cycle: two cycles become one. The idle time was thrown away to speed up the chip, and the RFSH feature was the first thing to go. No chips, Z80 successors or otherwise, have it today. But they do, fortunately, all feature a single-instruction multiply.

    It would be nice to summarise such a long post with a little audio demo. One day, I’ll revisit this and tail it off nicely. Unfortunately, the most important contextual difference between a blog post and a book chapter is that right now, I am in thrall to the exigencies of the day, and I’m excused from ending it well. Let’s reconvene after my deadlines.

  • The Wobbly Bits

    This week, I ported the LFO and envelope code (including the delay feature for envelope 2, a sweet little pseudorandom number generator for the LFO, and all the original bodges to make the amplitude attack sound better), mapped the control surface properly to the synth controls, and translated the original OSCar patches from the old layout into the new one.

    The patch data translation was achieved using a Python script I wrote, because that’s what Python is for. While it was changing the order, range, and zero-value points for the parameters, it also decoded them into something more readable:

    2 : CELLO
    --------------------------------
    Osc1Wave:       Pulse
    Osc2Wave:       =Osc1
    Osc2Oct:        +0
    Osc2Semi:       +0
    Osc2Detune:     +8
    Octave:         +0
    Transpose:      +0
    BendAmt:        0x1d
    PulseWidth:     +35
    OscBalance:     -2
    NoiseBalance:   -64
    GlideType:      Normal/Fix
    GlideRate:      0x00
    FiltType:       LowPass
    FiltFreq:       0x9c
    FiltQ:          0x21
    FiltSeparation: 0x4b
    FiltDrive:      0x71
    LFOWave:        Triangle
    LFORate:        0x60
    IntroDelay:     0x40
    EAAttack:       0x4e
    EADecay:        0x00
    EASustain:      0x7f
    EARelease:      0x2f
    EFAttack:       0x54
    EFDecay:        0x00
    EFSustain:      0x7f
    EFRelease:      0x34
    VoiceMode:      Normal
    TriggerType:    Single
    ExtAndSync:     0
    ArpFlags:       Up-Down-Rpt hands-on
    GateTime:       0x30
    WheelToPitch:   0x0c
    WheelToFilt:    0x00
    LFOToPitch:     +9
    LFOToFilt:      +5
    EnvToFilt:      -12

    Pulse is an interesting choice of basic waveform for cello: Helmholtz told us to start with a sawtooth, but it’s not that different in the frequency domain.

    Once I’ve finished tinkering with the memory layout, I have to come up with a way for the new OSCar to accept a patch dump from the old one, and vice versa. It’s complicated by the all-or-nothing nature of OSCar’s Sysex format, and the fact that I don’t want to have to write converters in both directions in firmware. There’s probably going to have to be a Paul Whittington Music patch librarian at some point: thank goodness (well, thank Jules) for JUCE.

    Three new LEDs on the control panel show LFO and envelope levels, and they do their job nicely enough now, but two things are preventing me from filming another demo. The first is that the Jewish calendar has a festival at this time of year. I’m visiting my mother for Pesach, and didn’t want to schlep a full-sized synth on the train with me. The second is I didn’t quite finish the modulation maths, which is surprisingly sprawling, taking in the data from all these modulators, testing for all kinds of mode-based exceptions, and mixing via a lot of arithmetic and mod wheel signals to the filter and amplitude control voltages.

    This will be the last progress-o-gram, for a few reasons:

    1. Little remains to be ported of OSCar’s code that actually affects the way it sounds or operates. From now on, a short list will serve better.
    2. This has always been a loose abstraction, which is another way of signalling that it has passed the stage where it was serving a useful purpose and is now telling lies. It’s a question of taste to decide when a routine deserves to turn green. xpot.mac, for example, converts 8-bit control values to the 16-bit numbers that the synth engine uses internally. It includes functions to rescale, exponentiate, and reverse the direction of some controls. While this stuff is all converted and tested, a few of the parameters that use this pipeline aren’t wired in yet. That’s one reason why it’s not marked as done. The other is that the code has changed context and location. Whereas the original codebase had to deal with a control surface, it’s needed to become more abstract so it can handle MIDI data too. At risk of labouring the point, kwave.mac is green but the keyboard waveforms aren’t usable yet. (Clerical error.) On the other hand, the two routines left in orange, that compute the control voltages for filters and amplifiers and live-edit the wave tables to modulate the pulse width respectively, are sufficiently understood to make a start on conversion even if the source code isn’t yet documented to my usual fussy standard.
    3. As it’s the precise character, quirks, and workflow of the original we want to emulate, and not its technical limitations, most of the rest of the work is going to be coupled less closely to the original OSCar. That 1-kilobyte lump of MIDI code is very much a case in point. The abstraction will only hold worse from now on.

    Since we’re leaving the progress-o-gram behind, here’s my to-do list, organised into the only two categories that currently matter. It’s still looking good for a publicly-playable demo by Superbooth on May 7th. We’ll be doing this with the current iteration of prototype, complete with little green wires, as it’ll save me a day of administering and building a new PCB.

    Before Superbooth:

    • Making the modulation engine work the 7 internal control voltages properly (of which two are currently complete and the rest are bodged with falsework code). At this point, OSCar will suddenly sound like OSCar;
    • Adding the two LFO settings that aren’t actually LFO settings: keyboard control voltage and envelope;
    • ‘Hold’ mode, which seems to work quite differently to the same feature on Mininova and Mantis;
    • Adding the pulse waveform modes and the special LFOs that control them according to their own weird rules;
    • Adding all pitch control logic, including glide, tuning, transpose, and the pitch bend wheel;
    • Lighting up the panel octave LEDs;
    • Temporary patch recall so that we can audition the original factory presets;
    • If I can be bothered, designing and 3D printing a temporary top panel just to make the prototype look more like an OSCar.

    After Superbooth:

    • The user wavetable editor;
    • External tempo sync;
    • Advanced triggering options that the original ‘TRIGGERING’ knob supported;
    • Full preset management: loading, saving, and MIDI dumps;
    • Fixing the architectural mess that Chris left with the wavetable oscillators (on which, more in good time) so that, without changing the sound, we’ll be able to accommodate a wider range than the original 60 semitones.
    • The arpeggiator;
    • The sequencer;
    • Mysterious and exciting new features, which I know better than to promise in any detail.
    The TRIGGERING and FUNCTION knobs are going to take a lot of attention after Superbooth. The masking tape holds the button caps in, by the way. This is supposed to be achieved by rubber sleeves but those perish over time, and I’m not allowed to take this OSCar apart to repair it because it really belongs in the V&A Museum.

  • More about the source code

    The disadvantage of being self-employed is that life gets in the way sometimes, and the past week had to be spent in ways that don’t advance the plot. The advantage is that I’m able to let this happen without having to answer to a boss. Some weeks are slow, and last week was one of them.

    I’m quietly getting on with putting a lot of the modulation routines together — rather tedious parameter wrangling, 8-bit maths with a load of variables for signalling and storing intermediate states, and unit testing across two platforms. It gives me little to show until the whole thing is ready. At some point we’ll have another demo, but not today. The progress-o-gram is barely flashing at all:

    Whenever the ‘slow week’ flag is set, I get to pop the stack and talk about one of the things I said I’d discuss in a previous post. At the top of this stack: working with the source code.

    For about the first five months of this project, from late spring to autumn last year, I’d assumed OSCar’s source code to be lost: it’s over forty years old, Chris is no longer around to tell us where to find it, and we didn’t know where he kept it.

    I found one lead, when I happened across a video by Dave Spiers of Geforce who, apparently, had received an archive of OSCar source from Chris as he was designing the first impOSCar back in the early 2000s. If you watch the video to the end, though, you’ll see why I didn’t expect him necessarily to be good-humoured, chatty, and forthcoming to a stranger about his work at the moment.

    The other lead was Bob Grieb at Tauntek, to whom I was steered by the OSCar Synth Facebook group, but he gave me the impression that he’d only disassembled the parts of code that he’d needed to improve, and wasn’t going to give it to me for free. At that time, Paul Whittington’s mind was on more pressing matters, so any kind of commercial negotiation was not going to be worth the time I’d have to wait.

    My first enquiries came to nothing, and I’m not the kind of person to make a nuisance of myself. We had schematics and, because somebody copied the M2 EPROM and put it online, the object code is readily available. It’s only 8 kilobytes of Z80, so how hard can it be to reverse engineer?

    Not really hard, it turns out, but really, really time consuming (probably 500 hours). As an idea of the starting point, somebody has pushed an automatically-generated OSCar disassembly onto GitHub here, presumably as an exercise for the reader. See how easy it is to drop your cursor anywhere in this file and understand what’s going on at that point.

    Ghidra

    First, I loaded the object code into Ghidra, and let that disassemble and analyse it. Ghidra’s an open-source Java-based tool that the US National Security Agency designed for reverse-engineering malware. They added Z80 to its list of competencies for, well, some reason: old military tech still uses it, I suppose. Ghidra quietly does the best job that any free, dumb tool could possibly hope to do. It makes reasonable (but fallible) guesses about which parts of the binary are data and which parts are code, and lets you edit those decisions with a few keystrokes, rename labels, and add your own comments. Somebody at the NSA bothered to read the chip’s datasheet too, so it understands how the Z80 divides its addressable spaces into memory and I/O, and where you’re going to find the interrupt service routines.

    Best of all, it builds a call tree, so you can see which code links to which other code, in both directions.

    When you start work like this, it’s like a Sudoku puzzle: you attack it in multiple places until you hit an inflection point where the code starts joining up, and suddenly you can start making sense of big, important parts of it. It turns out that there are a few reliably good starting places.

    Starting Place 1: the bottom of the call tree

    The green text starting XREF[11] in the image above was added by Ghidra to inform us that this little routine is called from 11 places in the code. There aren’t any CALL or JR instructions before the RET statement either, and no RAM is referenced: this code just does its job in eight instructions and returns. This makes it a great starting place for understanding what’s going on, and it’s the first routine I decoded.

    Ghidra will represent this as C for you, but that feature is really rotten. Every implicit decision it’s made in getting to this code obfuscates its purpose.

    void FUN_ram_1bca(ushort param_1,byte param_2)
    {
      bool bVar1;
      char cVar2;
      
      cVar2 = '\b';
      param_1 = param_1 & 0xff00;
      do {
        bVar1 = CARRY2(param_1,param_1);
        param_1 = param_1 * 2;
        if (bVar1) {
          param_1 = param_1 + param_2;
        }
        cVar2 = cVar2 + -1;
      } while (cVar2 != '\0');
      return;
    }
    

    How would I write this as C? Um …

    return E * H;

    It doesn’t emulate the side effects, but you only have to check in 11 places to see if those get used.

    I abandoned Ghidra after a month or so. In some ways, its treating of the source code as a sacred relic prevents accidental corruption by the user, but it makes adding comments or changing the way data is represented a pain in the arse.

    Instead, I exported my early work-in-progress as a couple of source files (ROM code and RAM data labels), and threw together a DOS batch file that would run them through a good free Python assembler I’d found online, before comparing its output with the EPROM binary I’d started with.

    Since then, I’ve been cleaning up the source exclusively in Notepad++, where I created a syntax highlighting scheme for Z80, and enjoy my new-found freedom. As long the batch file is run from time to time to prove that the code still assembles and matches the EPROM, I can comment and organise away and be confident that I’m not changing the code that it generates.

    Illuminating the symbols in RAM, quite a lot of which are bit fields reflecting the synth’s internal status, turns out to be most of the work. Aside from that, it’s adding comments and explanations, and giving the ROM routines sensible names to clarify the organisation of the code. The routines for the new synth are written in C and unit-tested against an emulation.

    FUN_ram_1bca is, as I have already revealed, the multiplier. The Z80 microprocessor pre-dates the inclusion of hardware multipliers in commodity silicon so, if you wanted to multiply two numbers together, you’d perform long multiplication in binary. Here’s my annotated Z80:

    MULTIPLY:
        ; long-multiplication
        ; the product H*E is returned in HL.
        ; on exit, E is preserved; B and D are 0.
        LD      B,8
        LD      D,0
        LD      L,D
    @loop:
        ; HL <<= 1
        ADD     HL,HL
        JR      NC,@no_carry
        ; HL += E
        ADD     HL,DE
    @no_carry:
        DJNZ    @-loop
        RET

    That’s the same 8-instruction routine labelled and commented up. Compared with the single-instruction multiply that you’d find in every modern microcontroller, it runs at a glacial speed. It takes an average of 83 microseconds to multiply two 8-bit numbers. That’s about 5% of the throughput you’d need to write a digital volume control for a CD player. (Yes, I know you could optimise for that case, but you’d still be nowhere near.) OSCar will stop what it’s doing, disable interrupts, and have a little think whenever it’s asked to rebuild the custom waveforms: apart from anything else, it’s got to call this routine more than 6,000 times.

    Starting Place 2: I/O routines

    The other handy place to attack code is wherever there’s an IN or OUT instruction. These are used exclusively for accessing the hardware. By this stage, I’d spent several days with a good schematic, annotating it and making several pages of typed tables so that I knew what was dangling off the peripheral bus. The IN and OUT port numbers then gave away what the processor was busy discovering. After finding the multiplication routines (there are three: two others add some code to handle offset binary numbers), the very next thing I found was the button-reading routine, which begins 136 bytes into the memory. This sets up the hardware to present the buttons to the peripheral chip, reads them over two turns, reorders a few to make them easier to handle in later routines, and places them in RAM. Anything that reads these locations in memory is therefore looking for held buttons. By comparing the buttons they’re checking against the user manual, I obtained a picture of what those routines were handling.

    The OSCar user manual became so indispensable that I spent a day OCRing it and made a searchable version along with a few extra tables that condensed its contents. We have fragments of the original document on our backups, but there are two scanned versions of the OSCar manual online, and we have a hard copy of our own. I cribbed from all of these. One scan, from an earlier firmware version, contains information about unimplemented or discarded features from the arpeggiator. I kept those details.

    Starting Place 3: MIDI handlers

    Chris did me a massive favour by putting MIDI compatibility into the synth. Once the IN and OUT instructions are found where it’s reading and writing to the UART, I could see it check for note messages, convert the pitch data into the bizarre internal note format that OSCar has (where the notes descend in pitch as the number increases), and set a couple of ‘notice me later’ flags. Again, this revealed the location of the voice management routines. The handlers for program change and System Exclusive messages revealed where the patch, waveform, and sequencer memory was located, and confirmed the location of the active program data. This then opened up even more of the synth.

    Bad starting places

    I covered this in my talk a little, but that isn’t published yet. You’d think that the main loop or interrupt service routines, with their parades of CALL instructions, would be good places to start. Or location 0 in memory. After all, that’s where the processor begins its task.

    It turns out that these are quite dispiriting starting places. All the areas of memory that they run, test, and initialise, are unmapped and indistinguishable when you start out. Here, for example, is the main loop at the start of the journey:

    @mainloop:
        CALL      FUN_ram_020e
        CALL      FUN_ram_09ac
        CALL      FUN_ram_1870
        CALL      FUN_ram_0dae
        CALL      FUN_ram_0e30
        CALL      FUN_ram_0aed
        CALL      FUN_ram_0a35
        CALL      FUN_ram_13c4
        CALL      FUN_ram_146d
        CALL      FUN_ram_04e7
        CALL      FUN_ram_1edb
        CALL      FUN_ram_087d 
        JR        @-mainloop

    The breadth of functionality that you don’t yet understand taunts you: an ever-present testimony to the incompleteness of the work.

    Now this feels like cheating

    In around September, my labour on the disassembly slowed down: I’d recovered about 60% of the source code with what turned out to be a good, if imperfect, degree of accuracy. (I guessed the purposes of a few bytes of RAM incorrectly when I got bored. This isn’t my first digital synth, and some routines were really boring.) I tried using Claude to help at one point, but Claude is rather more enthusiastic than competent at this class of task, and confidently labelled up routines in ways that were wrong most of the time. I’d deciphered almost everything except the arpeggiator, sequencer, and some of the deeper parts of the synth like modulation, voice management, and envelope generation. I knew exactly where to find them by now, but they were still unlabelled and unexplained.

    Then Paul remembered, amid the flotsam he’d inherited from the Oxford Synthesiser Company, a stack of 5.25″ backup disks, marked as formatted on either MS/DOS or, for earlier ones, a couple of flavours of CP/M.

    First I lent these to a very trusted friend who volunteers at the National Museum of Computing, who had a stack of hardware he could work with. He didn’t get that far with them. He’d noticed that some of the disks didn’t read on both sides, concluded it was a fault with his drive, and went away to ponder the situation in his own time.

    Not wanting to be outdone I’d decided, by this stage, that my time was worth the £120 or so it would cost to buy an elderly but tested 5.25″ drive from eBay along with a Greaseweazle PCB. This acts as a specialist drive controller, and works with some command-line software to allow people to scan old disks at a very detailed level, make rhythmic whirring and clunking and buzzing noises that transport you back to a primary school computer room in late 1988, before, abruptly …

    I converted these flux-level scrapes to text-editor readable disk images. All the MS/DOS disks and some of the CP/M disks played nicely, but CP/M is actually a multitude of different formats, some of which were too obscure for modern-day filesystem emulators. They could, though, be loaded as one big lump of data into an editor, and inspected manually.

    At this stage, I got my first glimpse of some of the final-issue source code for OSCar, version M2A.

    Also the most faded label in Christendom. CPM 2.2N / OSCAR MIDI / BACK UP / OM2A APRIL 86

    This actually differs from the final shipped M2 version in two instructions, but the changes aren’t earth-shattering and probably not worth bothering with: they speed up the interrupt polling a little, and ensure that the control voltages get updated a little more often.

    I ran through all the disks. Some contained old schematic drawings for the Advanced Sound Generator in a format that I could not read, reverse-engineer properly, or find documented anywhere. Not really important in light of the fact we could recreate these from the actual device if we ever wanted to, but it would have been nice to view them. Claude had a go at extracting their contents (because why not) and didn’t manage to glean much from them either. Again, confident and wrong in equal measure.

    There were a lot of old WordStar documents too: personal letters from Chris, letters to suppliers and would-be sponsors in English and French, replete with trade prices; customised factsheets sent by post to distributors and customers; snatches of the user manual; general assembly BOMs, and so on. These documents were in one of those unreadable flavours of CP/M, and stored peculiarly, with the contents of different files interleaved. This suggests that, rather than backups, they were disks in daily use, containing work in progress. It reads rather like a stream of consciousness of the way we used to conduct business: everything, including phone calls, had to be summarised, resolved, or confirmed in letters sent through the post. The contents may one day be useful to somebody, but it’s more likely that they’ll remain forever curiosities.

    As it happens, the value of these files was tested recently when PWM was contacted to ask if we could help a customer replace the keybed wireform on their original OSCar that was beginning to corrode. I found a purchase order for the OSCar wireforms dated 19th August 1983, sent to Hawnt Electronics:

          ITEM     QTY     PART No    DESCRIPTION          # EACH  # LOT
          ----     ---     -------    -----------          ------  ------
    
           1       100     CABLE ASSEMBLY 17 WAY           1.80    180.00
                           2 x 7720-17 ON 7307-17
                           10 INCH CABLE
                           SEE ENCLOSED SAMPLE
    
           2       100     CABLE ASSEMBLY 5 WAY            0.61     61.00
                           1 x 7720-05 ON 8996-05
                           7 INCH CABLE.FREE END STRIPPED
                           SEE ENCLOSED SAMPLE
    
           3       200     6410-17  0.1" STRAIGHT PIN      0.41083  82.17
                           HEADER TO MATCH 17 WAY
                           CABLE ASSEMBLY
    
           4       100     6410-05  0.1" STRAIGHT PIN      0.15012  15.01
                           HEADER TO MATCH 5 WAY
                           CABLE ASSEMBLY
    
           5       100     4455-12AC  12 WAY RT. ANGLE     0.32905  32.90
                           SOCKET
    
           6       100     4455-11AC  11 WAY RT. ANGLE     0.30412  30.41
                           SOCKET

    It didn’t take much work to discover that Hawnt was bought by Deltron in 2001, who were absorbed into Avnet in 2010. At some point, the wireforms became obsolete and the catalogue numbers meaningless, so this stuff helped only by revealing the fact that we couldn’t help.

    Some disks could not be read well because the heads on Chris’s drive must have been misaligned at one point. These are the ones that appeared to be single-sided at the National Museum of Computing. The disk imaging software showed something there, however unreliably. I decided against de-calibrating my drive in order to try and recover the data, because I spent enough time playing with tape machines at university to realise the danger posed by the rarity of calibration media.

    Of course I was doing all this with the drive uncovered, so I watched as one disk sloughed off its oxide all over the head when I tried to read it. As I was doing a flux-level reading I let it continue: I figured there was a fair chance that the damage was happening after the head. Fortunately, we were lucky: it was a disk of old documents and we read them.

    The ‘after’ picture: this miserable disk shed its oxide in the drive. There were disk platters that appeared to be in far worse shape but read perfectly cleanly, and I didn’t see that kink at the bottom until long after the picture was taken, so I didn’t predict this happening.

    Anyway, Chris’s source was present on a few disks as a series of .MAC (‘MAchine Code’) files. These, and much of their history, were recovered. The first surprise is that he’d logically separated the source code into different files, whereas I’d just been knocking up a massive lump of code. The second was that he’d been working with with rather austere limitations. None of his variable names are longer than six characters. None of his lines exceeds 48 characters. Pretty much every line of code is commented, but in a pretty ugly way. A friend of mine who lived through this era and has written a lot of commercial code said ‘It looks like an electronic engineer trying to program’. To be fair to Chris, that’s exactly what he was, and I even have his CV from 1983 to prove it. He’d improved considerably by the time we did Mininova and Mantis, but his house style continued to carry a lot of the old habits.

    I suppose it should have been obvious from the outset that the editing tools available in 1983 would have limited Chris’s descriptive powers, as well as his ability to file comments in the obvious place. Commentary about the organisation of variables stored in RAM, for example, is usually in a related area of the ROM source. He was working at a time before there was an established feel for best practice in coding, and when juggling files larger than a few tens of kilobytes would have been very unpleasant. My more senior friend rebuffs these excuses of mine, but that friend had the privilege of working with especially talented programmers at the time. Now we have an Internet full of good examples, any number of books about the subject and, if you’ve ever worked in a coding team, you will have had house styles and label and comment discipline drummed into you.

    Here’s Chris’s version of the multiply routine:

    ; MULTIPLY ROUTINE. H IS MULTIPLIER
    ;                   E IS MULTIPLICAND
    ;                   HL BECOMES PRODUCT
    
    MULT:  LD B,8         ;BITS
           LD D,0         ;CLEAR D
           LD L,D         ;CLEAR L
    MUL1:  ADD HL,HL      ;L.SHIFT
           JR NC,MUL2     ;SKIP ADD
           ADD HL,DE      ;ADD TO INTERMEDIATE RESULT
    MUL2:  DJNZ MUL1      ;8 BITS
           RET

    It’s a representative example. No lowercase letters in sight. Every line has a comment, and not all of them add useful exposition. There’s no concept of ‘local’ branch names, so loops and skips just use the main call name with a number added. There’s a bit of white space for the eyes, but very few internal subdivisions outside the main routines. Comments are seldom afforded lines of their own.

    But the alternative to working with this is nothing. Having worked intensively with nothing, this is much better. My own recovered source code follows better practices, benefits from a bigger screen and better editors; these days we also have source control. Clarity is improved and intention is clearer because it’s written by one person trying to reach into the mind of another. Aside from making it work with the pyz80 assembler, I’ve done nothing to my working copy of Chris’s source code: it’d feel like retouching a Da Vinci with a Sharpie. So I’m still working entirely with my own version. If I’m missing details, uncertain, or something seems off, I now have an original to check against. This is still hard enough. The routines that were left were the least tractable, so I’m working with a hint book more than an answer book — but it’s been invaluable in clearing up red herrings and answering open questions.

    The final part of this tale is that Paul eventually found, very deep in some data we inherited from Chris’s old computer, a file called oscar.zip, date-stamped 15th May 2002. It contains a backup of the complete source code, along with an MS/DOS batch file to assemble it. Those files, containing the M2 firmware, are date-stamped 27th February 1986. This must be the archive that Chris sent to GeForce. While it would have saved me a lot of time — an awful lot — if I’d happened across this data earlier, the hardest route tends to have its compensations.

    Bonus content

    Backups of pre-MIDI versions of code are intriguingly different from the final OSCar firmware. Because Chris had to free up a lot of memory for the MIDI features, the internal sine table used to be twice as long as it eventually became. This enabled it to be appreciably less distorted than the M2 version. I now have a choice between two authenticities when I get that feature going. The earlier version is probably definitive, the later a compromise to save memory. However, absolutely everybody with an OSCar is now driving with the later firmware, so that probably wins the argument.

    The other useful feature in earlier versions of OSCar is the factory presets, which I wasn’t looking forward to trying to scrape from a wrinkled cassette. They are stored in ROM, so appear in the source code files in their unexpurgated glory. With names!

    ; 2
    ; VOICE CELLO
    
           DEFB 0E3H  ; FILTER DRIVE
           DEFB 07DH  ; OSC. BALANCE
           DEFB 001H  ; NOISE BALANCE
           DEFB 09CH  ; FILTER FREQUENCY
           DEFB 097H  ; FILTER SEPARATION
           DEFB 0FEH  ; SUSTAIN LEVEL 1
           DEFB 03AH  ; BEND AMOUNT
           DEFB 018H  ; PITCH AMOUNT
           DEFB 000H  ; FILTER AMOUNT
           DEFB 0C6H  ; PULSE WIDTH
           DEFB 043H  ; Q
           DEFB 0FEH  ; SUSTAIN LEVEL 2
           DEFB 091H  ; DETUNE
           DEFB 08BH  ; DIRECT FILTER MOD
           DEFB 069H  ; ENV.AMOUNT TO FILTER
           DEFB 092H  ; DIRECT PITCHMOD.
           DEFB 060H  ; GATE TIME
           DEFB 09CH  ; ATTACK 1
           DEFB 05EH  ; RELEASE 1
           DEFB 001H  ; DECAY 1
           DEFB 0A8H  ; ATTACK 2
           DEFB 069H  ; RELEASE 2
           DEFB 001H  ; DECAY 2
           DEFB 001H  ; GLIDE RATE
           DEFB 0C0H  ; LFO RATE
           DEFB 080H  ; INTRO TIME
           DEFB 043H  ; GLIDE TYPE,WAVE1
           DEFB 052H  ; WAVE2,OCT SHIFT
           DEFB 000H  ; LFO TYPE,FUNCTION
           DEFB 000H  ; TRIG TYPE,FILTER TYPE
           DEFB 077H  ; KPITCH,KTUNE
           DEFB 027H  ; OCT 1,ARPST

    I believe this file, and possibly our cassette, to contain he only extant record of the original factory presets. The demos and files online that presume to demonstrate them are not showcasing these.

    No such thing as a free lunch, though: some of these parameters could do with tidying up a little. They’re going to be reordered and mostly reduced to 7 bits. That’s their meaningful resolution, so it won’t change the sound at all. As Paul Wiffen confided to me last year, they are indeed mostly the same initial patch with a few small tweaks. We pay good money for sound designers these days and our standards are higher: these are going to require combing and curation for our own version of OSCar. But they’re authentic. They’ll sound like Thatcher’s Britain, and they’re ours again.

    Post Scriptum: To publish or not to publish

    My instinct is to pay this kind of work forward. The entire software industry builds its work on the generosity of engineers unglamorously toiling and sharing modules they’ve made in their own time, out of either leisure or necessity, without expectation of payment. Chris’s code looks so old-fashioned today because generations of professional programmers have been generous enough to let us appreciate well-written source code.

    What’s stopping me is Behringer. They’ve done me before, they’ve done Chris before with the WASP, and they’ve set their sights on OSCar. I’d be a fool to make it easy for them, and it’s why I’ve used a rather trivial example of our code above, when a longer routine would have better illustrated my opinions on style.

    Trust is a commons like any other: it can be plundered for private gain, and that will impoverish the ecosystem. Unless we proceed with care, IP theft and strategic lawfare will bring us low prices, but will lead to an industry that produces mountains of instant landfill, a hollow parody of variety, and products with neither true novelty nor intrinsic value. Good products take time and accommodation for failure. A pressure to depress wages, timeline, and material costs at the expense of practitioners and customers is already underway in fashion and food, and I’m buggered if I help to normalise it in my own industry.

    The downside is that I sometimes have to stop myself from sharing nice things when I want to, which makes me a different kind of conspirator.

    So, here’s the deal: I’ll publish my version of the OSCar source at some point. It represents hundreds of hours of work from somebody who’s been doing this for a while and who actually worked closely with Chris. I think it compares well with the quality of the code it describes, but it’s of absolutely no use to anyone rotting on my hard drive. Publishing it, even under a restrictive licence, would help other people to fix the bugs in the Z80 OSCar, replace the cassette routines with (perhaps) something more useful, and so on. Provided, of course, that anybody is actually willing to do so. Working in Z80 still isn’t much fun: my significant contribution to the legacy of OSCar will [I hope] be to drop a new processor into it and turn it into a modern instrument without changing its sound or damaging its essence.

    But any kind of source code release will have to wait until our own synth has been out for a while. Watch this space a little longer.